@ -2,6 +2,7 @@ import 'dart:async';
import ' dart:typed_data ' ;
import ' dart:typed_data ' ;
import ' package:flutter/material.dart ' ;
import ' package:flutter/material.dart ' ;
import ' package:provider/provider.dart ' ;
import ' package:provider/provider.dart ' ;
import ' ../l10n/l10n.dart ' ;
import ' ../models/contact.dart ' ;
import ' ../models/contact.dart ' ;
import ' ../connector/meshcore_connector.dart ' ;
import ' ../connector/meshcore_connector.dart ' ;
import ' ../connector/meshcore_protocol.dart ' ;
import ' ../connector/meshcore_protocol.dart ' ;
@ -32,7 +33,6 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
bool _refreshingLocation = false ;
bool _refreshingLocation = false ;
bool _refreshingRepeat = false ;
bool _refreshingRepeat = false ;
bool _refreshingAllowReadOnly = false ;
bool _refreshingAllowReadOnly = false ;
bool _refreshingPrivacy = false ;
bool _refreshingAdvertisement = false ;
bool _refreshingAdvertisement = false ;
StreamSubscription < Uint8List > ? _frameSubscription ;
StreamSubscription < Uint8List > ? _frameSubscription ;
RepeaterCommandService ? _commandService ;
RepeaterCommandService ? _commandService ;
@ -246,17 +246,6 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
} ) ;
} ) ;
}
}
bool _isAnySectionRefreshing ( ) {
return _refreshingBasic | |
_refreshingRadio | |
_refreshingTxPower | |
_refreshingLocation | |
_refreshingRepeat | |
_refreshingAllowReadOnly | |
_refreshingPrivacy | |
_refreshingAdvertisement ;
}
bool _normalizeOnOff ( String value ) {
bool _normalizeOnOff ( String value ) {
final normalized = value . trim ( ) . toLowerCase ( ) ;
final normalized = value . trim ( ) . toLowerCase ( ) ;
return normalized = = ' on ' | |
return normalized = = ' on ' | |
@ -398,6 +387,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
required ValueSetter < bool > setRefreshing ,
required ValueSetter < bool > setRefreshing ,
} ) async {
} ) async {
if ( _commandService = = null ) return ;
if ( _commandService = = null ) return ;
final l10n = context . l10n ;
setState ( ( ) {
setState ( ( ) {
setRefreshing ( true ) ;
setRefreshing ( true ) ;
@ -426,14 +416,14 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
if ( successCount > 0 ) {
if ( successCount > 0 ) {
ScaffoldMessenger . of ( context ) . showSnackBar (
ScaffoldMessenger . of ( context ) . showSnackBar (
SnackBar (
SnackBar (
content: Text ( ' $ label refreshed ' ) ,
content: Text ( l10n . repeater_refreshed ( label ) ) ,
backgroundColor: Colors . green ,
backgroundColor: Colors . green ,
) ,
) ,
) ;
) ;
} else {
} else {
ScaffoldMessenger . of ( context ) . showSnackBar (
ScaffoldMessenger . of ( context ) . showSnackBar (
SnackBar (
SnackBar (
content: Text ( ' Error refreshing $ label ' ) ,
content: Text ( l10n . repeater_errorRefreshing ( label ) ) ,
backgroundColor: Colors . red ,
backgroundColor: Colors . red ,
) ,
) ,
) ;
) ;
@ -449,64 +439,63 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
}
}
Future < void > _refreshBasicSettings ( ) async {
Future < void > _refreshBasicSettings ( ) async {
final l10n = context . l10n ;
await _refreshSection (
await _refreshSection (
label: ' Basic settings ' ,
label: l10n . repeater_basicSettings ,
commands: const [ ' get name ' ] ,
commands: const [ ' get name ' ] ,
setRefreshing: ( value ) = > _refreshingBasic = value ,
setRefreshing: ( value ) = > _refreshingBasic = value ,
) ;
) ;
}
}
Future < void > _refreshRadioSettings ( ) async {
Future < void > _refreshRadioSettings ( ) async {
final l10n = context . l10n ;
await _refreshSection (
await _refreshSection (
label: ' Radio settings ' ,
label: l10n . repeater_radioSettings ,
commands: const [ ' get radio ' ] ,
commands: const [ ' get radio ' ] ,
setRefreshing: ( value ) = > _refreshingRadio = value ,
setRefreshing: ( value ) = > _refreshingRadio = value ,
) ;
) ;
}
}
Future < void > _refreshTxPower ( ) async {
Future < void > _refreshTxPower ( ) async {
final l10n = context . l10n ;
await _refreshSection (
await _refreshSection (
label: ' TX power ' ,
label: l10n . repeater_txPower ,
commands: const [ ' get tx ' ] ,
commands: const [ ' get tx ' ] ,
setRefreshing: ( value ) = > _refreshingTxPower = value ,
setRefreshing: ( value ) = > _refreshingTxPower = value ,
) ;
) ;
}
}
Future < void > _refreshLocationSettings ( ) async {
Future < void > _refreshLocationSettings ( ) async {
final l10n = context . l10n ;
await _refreshSection (
await _refreshSection (
label: ' Location settings ' ,
label: l10n . repeater_locationSettings ,
commands: const [ ' get lat ' , ' get lon ' ] ,
commands: const [ ' get lat ' , ' get lon ' ] ,
setRefreshing: ( value ) = > _refreshingLocation = value ,
setRefreshing: ( value ) = > _refreshingLocation = value ,
) ;
) ;
}
}
Future < void > _refreshRepeat ( ) async {
Future < void > _refreshRepeat ( ) async {
final l10n = context . l10n ;
await _refreshSection (
await _refreshSection (
label: ' Packet forwarding ' ,
label: l10n . repeater_packetForwarding ,
commands: const [ ' get repeat ' ] ,
commands: const [ ' get repeat ' ] ,
setRefreshing: ( value ) = > _refreshingRepeat = value ,
setRefreshing: ( value ) = > _refreshingRepeat = value ,
) ;
) ;
}
}
Future < void > _refreshAllowReadOnly ( ) async {
Future < void > _refreshAllowReadOnly ( ) async {
final l10n = context . l10n ;
await _refreshSection (
await _refreshSection (
label: ' Guest access ' ,
label: l10n . repeater_guestAccess ,
commands: const [ ' get allow.read.only ' ] ,
commands: const [ ' get allow.read.only ' ] ,
setRefreshing: ( value ) = > _refreshingAllowReadOnly = value ,
setRefreshing: ( value ) = > _refreshingAllowReadOnly = value ,
) ;
) ;
}
}
Future < void > _refreshPrivacy ( ) async {
await _refreshSection (
label: ' Privacy mode ' ,
commands: const [ ' get privacy ' ] ,
setRefreshing: ( value ) = > _refreshingPrivacy = value ,
) ;
}
Future < void > _refreshAdvertisementSettings ( ) async {
Future < void > _refreshAdvertisementSettings ( ) async {
final l10n = context . l10n ;
await _refreshSection (
await _refreshSection (
label: ' Advertisement settings ' ,
label: l10n . repeater_advertisementSettings ,
commands: const [
commands: const [
' get advert.interval ' ,
' get advert.interval ' ,
' get flood.advert.interval ' ,
' get flood.advert.interval ' ,
@ -604,8 +593,8 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
if ( mounted ) {
if ( mounted ) {
ScaffoldMessenger . of ( context ) . showSnackBar (
ScaffoldMessenger . of ( context ) . showSnackBar (
const SnackBar (
SnackBar (
content: Text ( ' Settings saved successfully ' ) ,
content: Text ( context . l10n . repeater_settingsSaved ) ,
backgroundColor: Colors . green ,
backgroundColor: Colors . green ,
) ,
) ,
) ;
) ;
@ -618,7 +607,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
if ( mounted ) {
if ( mounted ) {
ScaffoldMessenger . of ( context ) . showSnackBar (
ScaffoldMessenger . of ( context ) . showSnackBar (
SnackBar (
SnackBar (
content: Text ( ' Error saving settings: $ e ' ) ,
content: Text ( context . l10n . repeater_errorSavingSettings ( e . toString ( ) ) ) ,
backgroundColor: Colors . red ,
backgroundColor: Colors . red ,
) ,
) ,
) ;
) ;
@ -637,6 +626,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
Widget _buildSectionHeader ( {
Widget _buildSectionHeader ( {
required IconData icon ,
required IconData icon ,
required String title ,
required String title ,
required String tooltip ,
required bool isRefreshing ,
required bool isRefreshing ,
required VoidCallback onRefresh ,
required VoidCallback onRefresh ,
} ) {
} ) {
@ -658,7 +648,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
)
)
: const Icon ( Icons . refresh ) ,
: const Icon ( Icons . refresh ) ,
onPressed: isRefreshing ? null : onRefresh ,
onPressed: isRefreshing ? null : onRefresh ,
tooltip: ' Refresh $ title ' ,
tooltip: tooltip ,
) ,
) ,
] ,
] ,
) ;
) ;
@ -688,6 +678,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
@ override
@ override
Widget build ( BuildContext context ) {
Widget build ( BuildContext context ) {
final l10n = context . l10n ;
final connector = context . watch < MeshCoreConnector > ( ) ;
final connector = context . watch < MeshCoreConnector > ( ) ;
final repeater = _resolveRepeater ( connector ) ;
final repeater = _resolveRepeater ( connector ) ;
final isFloodMode = repeater . pathOverride = = - 1 ;
final isFloodMode = repeater . pathOverride = = - 1 ;
@ -698,7 +689,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
crossAxisAlignment: CrossAxisAlignment . start ,
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisSize: MainAxisSize . min ,
mainAxisSize: MainAxisSize . min ,
children: [
children: [
const Text ( ' Repeater Settings ' ) ,
Text ( l10n . repeater_settingsTitle ) ,
Text (
Text (
repeater . name ,
repeater . name ,
style: const TextStyle ( fontSize: 14 , fontWeight: FontWeight . normal ) ,
style: const TextStyle ( fontSize: 14 , fontWeight: FontWeight . normal ) ,
@ -709,7 +700,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
actions: [
actions: [
PopupMenuButton < String > (
PopupMenuButton < String > (
icon: Icon ( isFloodMode ? Icons . waves : Icons . route ) ,
icon: Icon ( isFloodMode ? Icons . waves : Icons . route ) ,
tooltip: ' Routing mode ' ,
tooltip: l10n . repeater_routingMode ,
onSelected: ( mode ) async {
onSelected: ( mode ) async {
if ( mode = = ' flood ' ) {
if ( mode = = ' flood ' ) {
await connector . setPathOverride ( repeater , pathLen: - 1 ) ;
await connector . setPathOverride ( repeater , pathLen: - 1 ) ;
@ -728,7 +719,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
Icon ( Icons . auto_mode , size: 20 , color: ! isFloodMode ? Theme . of ( context ) . primaryColor : null ) ,
Icon ( Icons . auto_mode , size: 20 , color: ! isFloodMode ? Theme . of ( context ) . primaryColor : null ) ,
const SizedBox ( width: 8 ) ,
const SizedBox ( width: 8 ) ,
Text (
Text (
' Auto (use saved path) ' ,
l10n . repeater_autoUseSavedPath ,
style: TextStyle (
style: TextStyle (
fontWeight: ! isFloodMode ? FontWeight . bold : FontWeight . normal ,
fontWeight: ! isFloodMode ? FontWeight . bold : FontWeight . normal ,
) ,
) ,
@ -743,7 +734,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
Icon ( Icons . waves , size: 20 , color: isFloodMode ? Theme . of ( context ) . primaryColor : null ) ,
Icon ( Icons . waves , size: 20 , color: isFloodMode ? Theme . of ( context ) . primaryColor : null ) ,
const SizedBox ( width: 8 ) ,
const SizedBox ( width: 8 ) ,
Text (
Text (
' Force Flood Mode ' ,
l10n . repeater_forceFloodMode ,
style: TextStyle (
style: TextStyle (
fontWeight: isFloodMode ? FontWeight . bold : FontWeight . normal ,
fontWeight: isFloodMode ? FontWeight . bold : FontWeight . normal ,
) ,
) ,
@ -755,14 +746,14 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
) ,
) ,
IconButton (
IconButton (
icon: const Icon ( Icons . timeline ) ,
icon: const Icon ( Icons . timeline ) ,
tooltip: ' Path management ' ,
tooltip: l10n . repeater_pathManagement ,
onPressed: ( ) = > PathManagementDialog . show ( context , contact: repeater ) ,
onPressed: ( ) = > PathManagementDialog . show ( context , contact: repeater ) ,
) ,
) ,
if ( _hasChanges )
if ( _hasChanges )
TextButton . icon (
TextButton . icon (
onPressed: _isLoading ? null : _saveSettings ,
onPressed: _isLoading ? null : _saveSettings ,
icon: const Icon ( Icons . save ) ,
icon: const Icon ( Icons . save ) ,
label: const Text ( ' Save ' ) ,
label: Text ( l10n . common_save ) ,
) ,
) ,
] ,
] ,
) ,
) ,
@ -791,6 +782,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
}
}
Widget _buildBasicSettingsCard ( ) {
Widget _buildBasicSettingsCard ( ) {
final l10n = context . l10n ;
return Card (
return Card (
child: Padding (
child: Padding (
padding: const EdgeInsets . all ( 16 ) ,
padding: const EdgeInsets . all ( 16 ) ,
@ -799,27 +791,28 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
children: [
children: [
_buildSectionHeader (
_buildSectionHeader (
icon: Icons . settings ,
icon: Icons . settings ,
title: ' Basic Settings ' ,
title: l10n . repeater_basicSettings ,
tooltip: l10n . repeater_refreshBasicSettings ,
isRefreshing: _refreshingBasic ,
isRefreshing: _refreshingBasic ,
onRefresh: _refreshBasicSettings ,
onRefresh: _refreshBasicSettings ,
) ,
) ,
const Divider ( ) ,
const Divider ( ) ,
TextField (
TextField (
controller: _nameController ,
controller: _nameController ,
decoration: const InputDecoration (
decoration: InputDecoration (
labelText: ' Repeater Name ' ,
labelText: l10n . repeater_repeaterName ,
helperText: ' Display name for this repeater ' ,
helperText: l10n . repeater_repeaterNameHelper ,
border: OutlineInputBorder ( ) ,
border: const OutlineInputBorder ( ) ,
) ,
) ,
onChanged: ( _ ) = > _markChanged ( ) ,
onChanged: ( _ ) = > _markChanged ( ) ,
) ,
) ,
const SizedBox ( height: 16 ) ,
const SizedBox ( height: 16 ) ,
TextField (
TextField (
controller: _passwordController ,
controller: _passwordController ,
decoration: const InputDecoration (
decoration: InputDecoration (
labelText: ' Admin Password ' ,
labelText: l10n . repeater_adminPassword ,
helperText: ' Full access password ' ,
helperText: l10n . repeater_adminPasswordHelper ,
border: OutlineInputBorder ( ) ,
border: const OutlineInputBorder ( ) ,
) ,
) ,
obscureText: true ,
obscureText: true ,
onChanged: ( _ ) = > _markChanged ( ) ,
onChanged: ( _ ) = > _markChanged ( ) ,
@ -827,10 +820,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
const SizedBox ( height: 16 ) ,
const SizedBox ( height: 16 ) ,
TextField (
TextField (
controller: _guestPasswordController ,
controller: _guestPasswordController ,
decoration: const InputDecoration (
decoration: InputDecoration (
labelText: ' Guest Password ' ,
labelText: l10n . repeater_guestPassword ,
helperText: ' Read-only access password ' ,
helperText: l10n . repeater_guestPasswordHelper ,
border: OutlineInputBorder ( ) ,
border: const OutlineInputBorder ( ) ,
) ,
) ,
obscureText: true ,
obscureText: true ,
onChanged: ( _ ) = > _markChanged ( ) ,
onChanged: ( _ ) = > _markChanged ( ) ,
@ -842,6 +835,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
}
}
Widget _buildRadioSettingsCard ( ) {
Widget _buildRadioSettingsCard ( ) {
final l10n = context . l10n ;
return Card (
return Card (
child: Padding (
child: Padding (
padding: const EdgeInsets . all ( 16 ) ,
padding: const EdgeInsets . all ( 16 ) ,
@ -850,17 +844,18 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
children: [
children: [
_buildSectionHeader (
_buildSectionHeader (
icon: Icons . radio ,
icon: Icons . radio ,
title: ' Radio Settings ' ,
title: l10n . repeater_radioSettings ,
tooltip: l10n . repeater_refreshRadioSettings ,
isRefreshing: _refreshingRadio ,
isRefreshing: _refreshingRadio ,
onRefresh: _refreshRadioSettings ,
onRefresh: _refreshRadioSettings ,
) ,
) ,
const Divider ( ) ,
const Divider ( ) ,
TextField (
TextField (
controller: _freqController ,
controller: _freqController ,
decoration: const InputDecoration (
decoration: InputDecoration (
labelText: ' Frequency (MHz) ' ,
labelText: l10n . repeater_frequencyMhz ,
helperText: ' 300-2500 MHz ' ,
helperText: l10n . repeater_frequencyHelper ,
border: OutlineInputBorder ( ) ,
border: const OutlineInputBorder ( ) ,
suffixText: ' MHz ' ,
suffixText: ' MHz ' ,
) ,
) ,
keyboardType: const TextInputType . numberWithOptions ( decimal: true ) ,
keyboardType: const TextInputType . numberWithOptions ( decimal: true ) ,
@ -873,10 +868,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
Expanded (
Expanded (
child: TextField (
child: TextField (
controller: _txPowerController ,
controller: _txPowerController ,
decoration: const InputDecoration (
decoration: InputDecoration (
labelText: ' TX Power ' ,
labelText: l10n . repeater_txPower ,
helperText: ' 1-30 dBm ' ,
helperText: l10n . repeater_txPowerHelper ,
border: OutlineInputBorder ( ) ,
border: const OutlineInputBorder ( ) ,
suffixText: ' dBm ' ,
suffixText: ' dBm ' ,
) ,
) ,
keyboardType: TextInputType . number ,
keyboardType: TextInputType . number ,
@ -887,16 +882,16 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
_buildInlineRefreshButton (
_buildInlineRefreshButton (
isRefreshing: _refreshingTxPower ,
isRefreshing: _refreshingTxPower ,
onRefresh: _refreshTxPower ,
onRefresh: _refreshTxPower ,
tooltip: ' Refresh TX power ' ,
tooltip: l10n . repeater_refreshTxPower ,
) ,
) ,
] ,
] ,
) ,
) ,
const SizedBox ( height: 16 ) ,
const SizedBox ( height: 16 ) ,
DropdownButtonFormField < int > (
DropdownButtonFormField < int > (
initialValue: _bandwidth ,
initialValue: _bandwidth ,
decoration: const InputDecoration (
decoration: InputDecoration (
labelText: ' Bandwidth ' ,
labelText: l10n . repeater_bandwidth ,
border: OutlineInputBorder ( ) ,
border: const OutlineInputBorder ( ) ,
) ,
) ,
items: _bandwidthOptions . map ( ( bw ) {
items: _bandwidthOptions . map ( ( bw ) {
return DropdownMenuItem (
return DropdownMenuItem (
@ -916,9 +911,9 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
const SizedBox ( height: 16 ) ,
const SizedBox ( height: 16 ) ,
DropdownButtonFormField < int > (
DropdownButtonFormField < int > (
initialValue: _spreadingFactor ,
initialValue: _spreadingFactor ,
decoration: const InputDecoration (
decoration: InputDecoration (
labelText: ' Spreading Factor ' ,
labelText: l10n . repeater_spreadingFactor ,
border: OutlineInputBorder ( ) ,
border: const OutlineInputBorder ( ) ,
) ,
) ,
items: _spreadingFactorOptions . map ( ( sf ) {
items: _spreadingFactorOptions . map ( ( sf ) {
return DropdownMenuItem (
return DropdownMenuItem (
@ -938,9 +933,9 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
const SizedBox ( height: 16 ) ,
const SizedBox ( height: 16 ) ,
DropdownButtonFormField < int > (
DropdownButtonFormField < int > (
initialValue: _codingRate ,
initialValue: _codingRate ,
decoration: const InputDecoration (
decoration: InputDecoration (
labelText: ' Coding Rate ' ,
labelText: l10n . repeater_codingRate ,
border: OutlineInputBorder ( ) ,
border: const OutlineInputBorder ( ) ,
) ,
) ,
items: _codingRateOptions . map ( ( cr ) {
items: _codingRateOptions . map ( ( cr ) {
return DropdownMenuItem (
return DropdownMenuItem (
@ -964,6 +959,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
}
}
Widget _buildLocationSettingsCard ( ) {
Widget _buildLocationSettingsCard ( ) {
final l10n = context . l10n ;
return Card (
return Card (
child: Padding (
child: Padding (
padding: const EdgeInsets . all ( 16 ) ,
padding: const EdgeInsets . all ( 16 ) ,
@ -972,17 +968,18 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
children: [
children: [
_buildSectionHeader (
_buildSectionHeader (
icon: Icons . location_on ,
icon: Icons . location_on ,
title: ' Location Settings ' ,
title: l10n . repeater_locationSettings ,
tooltip: l10n . repeater_refreshLocationSettings ,
isRefreshing: _refreshingLocation ,
isRefreshing: _refreshingLocation ,
onRefresh: _refreshLocationSettings ,
onRefresh: _refreshLocationSettings ,
) ,
) ,
const Divider ( ) ,
const Divider ( ) ,
TextField (
TextField (
controller: _latController ,
controller: _latController ,
decoration: const InputDecoration (
decoration: InputDecoration (
labelText: ' Latitude ' ,
labelText: l10n . repeater_latitude ,
helperText: ' Decimal degrees (e.g., 37.7749) ' ,
helperText: l10n . repeater_latitudeHelper ,
border: OutlineInputBorder ( ) ,
border: const OutlineInputBorder ( ) ,
) ,
) ,
keyboardType: const TextInputType . numberWithOptions ( decimal: true , signed: true ) ,
keyboardType: const TextInputType . numberWithOptions ( decimal: true , signed: true ) ,
onChanged: ( _ ) = > _markChanged ( ) ,
onChanged: ( _ ) = > _markChanged ( ) ,
@ -990,10 +987,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
const SizedBox ( height: 16 ) ,
const SizedBox ( height: 16 ) ,
TextField (
TextField (
controller: _lonController ,
controller: _lonController ,
decoration: const InputDecoration (
decoration: InputDecoration (
labelText: ' Longitude ' ,
labelText: l10n . repeater_longitude ,
helperText: ' Decimal degrees (e.g., -122.4194) ' ,
helperText: l10n . repeater_longitudeHelper ,
border: OutlineInputBorder ( ) ,
border: const OutlineInputBorder ( ) ,
) ,
) ,
keyboardType: const TextInputType . numberWithOptions ( decimal: true , signed: true ) ,
keyboardType: const TextInputType . numberWithOptions ( decimal: true , signed: true ) ,
onChanged: ( _ ) = > _markChanged ( ) ,
onChanged: ( _ ) = > _markChanged ( ) ,
@ -1005,6 +1002,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
}
}
Widget _buildFeatureTogglesCard ( ) {
Widget _buildFeatureTogglesCard ( ) {
final l10n = context . l10n ;
return Card (
return Card (
child: Padding (
child: Padding (
padding: const EdgeInsets . all ( 16 ) ,
padding: const EdgeInsets . all ( 16 ) ,
@ -1015,16 +1013,16 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
children: [
children: [
Icon ( Icons . toggle_on , color: Theme . of ( context ) . textTheme . headlineSmall ? . color ) ,
Icon ( Icons . toggle_on , color: Theme . of ( context ) . textTheme . headlineSmall ? . color ) ,
const SizedBox ( width: 8 ) ,
const SizedBox ( width: 8 ) ,
const Text (
Text (
' Features ' ,
l10n . repeater_features ,
style: TextStyle ( fontSize: 18 , fontWeight: FontWeight . bold ) ,
style: const TextStyle ( fontSize: 18 , fontWeight: FontWeight . bold ) ,
) ,
) ,
] ,
] ,
) ,
) ,
const Divider ( ) ,
const Divider ( ) ,
_buildFeatureToggleRow (
_buildFeatureToggleRow (
title: ' Packet Forwarding ' ,
title: l10n . repeater_packetForwarding ,
subtitle: ' Enable repeater to forward packets ' ,
subtitle: l10n . repeater_packetForwardingSubtitle ,
value: _repeatEnabled ,
value: _repeatEnabled ,
isRefreshing: _refreshingRepeat ,
isRefreshing: _refreshingRepeat ,
onChanged: ( value ) {
onChanged: ( value ) {
@ -1034,10 +1032,11 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
_markChanged ( ) ;
_markChanged ( ) ;
} ,
} ,
onRefresh: _refreshRepeat ,
onRefresh: _refreshRepeat ,
refreshTooltip: l10n . repeater_refreshPacketForwarding ,
) ,
) ,
_buildFeatureToggleRow (
_buildFeatureToggleRow (
title: ' Guest Access ' ,
title: l10n . repeater_guestAccess ,
subtitle: ' Allow read-only guest access ' ,
subtitle: l10n . repeater_guestAccessSubtitle ,
value: _allowReadOnly ,
value: _allowReadOnly ,
isRefreshing: _refreshingAllowReadOnly ,
isRefreshing: _refreshingAllowReadOnly ,
onChanged: ( value ) {
onChanged: ( value ) {
@ -1047,11 +1046,12 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
_markChanged ( ) ;
_markChanged ( ) ;
} ,
} ,
onRefresh: _refreshAllowReadOnly ,
onRefresh: _refreshAllowReadOnly ,
refreshTooltip: l10n . repeater_refreshGuestAccess ,
) ,
) ,
/ / Privacy mode - hidden until fully implemented
/ / Privacy mode - hidden until fully implemented
/ / _buildFeatureToggleRow (
/ / _buildFeatureToggleRow (
/ / title: ' Privacy Mode ' ,
/ / title: l10n . repeater_privacyMode ,
/ / subtitle: ' Hide name/location in advertisements ' ,
/ / subtitle: l10n . repeater_privacyModeSubtitle ,
/ / value: _privacyMode ,
/ / value: _privacyMode ,
/ / isRefreshing: _refreshingPrivacy ,
/ / isRefreshing: _refreshingPrivacy ,
/ / onChanged: ( value ) {
/ / onChanged: ( value ) {
@ -1061,6 +1061,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
/ / _markChanged ( ) ;
/ / _markChanged ( ) ;
/ / } ,
/ / } ,
/ / onRefresh: _refreshPrivacy ,
/ / onRefresh: _refreshPrivacy ,
/ / refreshTooltip: l10n . repeater_refreshPrivacyMode ,
/ / ) ,
/ / ) ,
] ,
] ,
) ,
) ,
@ -1075,6 +1076,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
required bool isRefreshing ,
required bool isRefreshing ,
required ValueChanged < bool > onChanged ,
required ValueChanged < bool > onChanged ,
required VoidCallback onRefresh ,
required VoidCallback onRefresh ,
required String refreshTooltip ,
} ) {
} ) {
return Row (
return Row (
children: [
children: [
@ -1093,10 +1095,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
width: 18 ,
width: 18 ,
height: 18 ,
height: 18 ,
child: CircularProgressIndicator ( strokeWidth: 2 ) ,
child: CircularProgressIndicator ( strokeWidth: 2 ) ,
)
)
: const Icon ( Icons . refresh , size: 20 ) ,
: const Icon ( Icons . refresh , size: 20 ) ,
onPressed: isRefreshing ? null : onRefresh ,
onPressed: isRefreshing ? null : onRefresh ,
tooltip: ' Refresh $ title ' ,
tooltip: refreshTooltip ,
visualDensity: VisualDensity . compact ,
visualDensity: VisualDensity . compact ,
) ,
) ,
] ,
] ,
@ -1104,6 +1106,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
}
}
Widget _buildAdvertisementSettingsCard ( ) {
Widget _buildAdvertisementSettingsCard ( ) {
final l10n = context . l10n ;
return Card (
return Card (
child: Padding (
child: Padding (
padding: const EdgeInsets . all ( 16 ) ,
padding: const EdgeInsets . all ( 16 ) ,
@ -1112,22 +1115,23 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
children: [
children: [
_buildSectionHeader (
_buildSectionHeader (
icon: Icons . broadcast_on_personal ,
icon: Icons . broadcast_on_personal ,
title: ' Advertisement Settings ' ,
title: l10n . repeater_advertisementSettings ,
tooltip: l10n . repeater_refreshAdvertisementSettings ,
isRefreshing: _refreshingAdvertisement ,
isRefreshing: _refreshingAdvertisement ,
onRefresh: _refreshAdvertisementSettings ,
onRefresh: _refreshAdvertisementSettings ,
) ,
) ,
const Divider ( ) ,
const Divider ( ) ,
ListTile (
ListTile (
title: const Text ( ' Local Advertisement Interval ' ) ,
title: Text ( l10n . repeater_localAdvertInterval ) ,
subtitle: Text ( ' $ _advertInterval minutes ' ) ,
subtitle: Text ( l10n . repeater_localAdvertIntervalMinutes ( _advertInterval ) ) ,
trailing: Text ( ' ${ _advertInterval } m ' ) ,
trailing: Text ( l10n . repeater_localAdvertIntervalMinutes ( _advertInterval ) ) ,
) ,
) ,
Slider (
Slider (
value: _advertInterval . toDouble ( ) ,
value: _advertInterval . toDouble ( ) ,
min: 60 ,
min: 60 ,
max: 240 ,
max: 240 ,
divisions: 18 ,
divisions: 18 ,
label: ' ${ _advertInterval } m ' ,
label: l10n . repeater_localAdvertIntervalMinutes ( _advertInterval ) ,
onChanged: ( value ) {
onChanged: ( value ) {
setState ( ( ) {
setState ( ( ) {
_advertInterval = value . toInt ( ) ;
_advertInterval = value . toInt ( ) ;
@ -1137,16 +1141,16 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
) ,
) ,
const SizedBox ( height: 16 ) ,
const SizedBox ( height: 16 ) ,
ListTile (
ListTile (
title: const Text ( ' Flood Advertisement Interval ' ) ,
title: Text ( l10n . repeater_floodAdvertInterval ) ,
subtitle: Text ( ' $ _floodAdvertInterval hours ' ) ,
subtitle: Text ( l10n . repeater_floodAdvertIntervalHours ( _floodAdvertInterval ) ) ,
trailing: Text ( ' ${ _floodAdvertInterval } h ' ) ,
trailing: Text ( l10n . repeater_floodAdvertIntervalHours ( _floodAdvertInterval ) ) ,
) ,
) ,
Slider (
Slider (
value: _floodAdvertInterval . toDouble ( ) ,
value: _floodAdvertInterval . toDouble ( ) ,
min: 3 ,
min: 3 ,
max: 48 ,
max: 48 ,
divisions: 45 ,
divisions: 45 ,
label: ' ${ _floodAdvertInterval } h ' ,
label: l10n . repeater_floodAdvertIntervalHours ( _floodAdvertInterval ) ,
onChanged: ( value ) {
onChanged: ( value ) {
setState ( ( ) {
setState ( ( ) {
_floodAdvertInterval = value . toInt ( ) ;
_floodAdvertInterval = value . toInt ( ) ;
@ -1158,16 +1162,16 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
/ / if ( _privacyMode ) . . . [
/ / if ( _privacyMode ) . . . [
/ / const SizedBox ( height: 16 ) ,
/ / const SizedBox ( height: 16 ) ,
/ / ListTile (
/ / ListTile (
/ / title: const Text ( ' Encrypted Advertisement Interval ' ) ,
/ / title: Text ( l10n . repeater_encryptedAdvertInterval ) ,
/ / subtitle: Text ( ' $ _privAdvertInterval minutes ' ) ,
/ / subtitle: Text ( l10n . repeater_localAdvertIntervalMinutes ( _privAdvertInterval ) ) ,
/ / trailing: Text ( ' ${ _privAdvertInterval } m ' ) ,
/ / trailing: Text ( l10n . repeater_localAdvertIntervalMinutes ( _privAdvertInterval ) ) ,
/ / ) ,
/ / ) ,
/ / Slider (
/ / Slider (
/ / value: _privAdvertInterval . toDouble ( ) ,
/ / value: _privAdvertInterval . toDouble ( ) ,
/ / min: 30 ,
/ / min: 30 ,
/ / max: 240 ,
/ / max: 240 ,
/ / divisions: 21 ,
/ / divisions: 21 ,
/ / label: ' ${ _privAdvertInterval } m ' ,
/ / label: l10n . repeater_localAdvertIntervalMinutes ( _privAdvertInterval ) ,
/ / onChanged: ( value ) {
/ / onChanged: ( value ) {
/ / setState ( ( ) {
/ / setState ( ( ) {
/ / _privAdvertInterval = value . toInt ( ) ;
/ / _privAdvertInterval = value . toInt ( ) ;
@ -1183,6 +1187,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
}
}
Widget _buildDangerZoneCard ( ) {
Widget _buildDangerZoneCard ( ) {
final l10n = context . l10n ;
final colorScheme = Theme . of ( context ) . colorScheme ;
final colorScheme = Theme . of ( context ) . colorScheme ;
return Card (
return Card (
color: colorScheme . errorContainer ,
color: colorScheme . errorContainer ,
@ -1196,7 +1201,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
Icon ( Icons . warning , color: colorScheme . onErrorContainer ) ,
Icon ( Icons . warning , color: colorScheme . onErrorContainer ) ,
const SizedBox ( width: 8 ) ,
const SizedBox ( width: 8 ) ,
Text (
Text (
' Danger Zone ' ,
l10n . repeater_dangerZone ,
style: TextStyle (
style: TextStyle (
fontSize: 18 ,
fontSize: 18 ,
fontWeight: FontWeight . bold ,
fontWeight: FontWeight . bold ,
@ -1208,14 +1213,14 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
const Divider ( ) ,
const Divider ( ) ,
ListTile (
ListTile (
leading: Icon ( Icons . refresh , color: colorScheme . onErrorContainer ) ,
leading: Icon ( Icons . refresh , color: colorScheme . onErrorContainer ) ,
title: Text ( ' Reboot Repeater ' , style: TextStyle ( color: colorScheme . onErrorContainer ) ) ,
title: Text ( l10n . repeater_rebootRepeater , style: TextStyle ( color: colorScheme . onErrorContainer ) ) ,
subtitle: Text (
subtitle: Text (
' Restart the repeater device ' ,
l10n . repeater_rebootRepeaterSubtitle ,
style: TextStyle ( color: colorScheme . onErrorContainer . withValues ( alpha: 0.8 ) ) ,
style: TextStyle ( color: colorScheme . onErrorContainer . withValues ( alpha: 0.8 ) ) ,
) ,
) ,
onTap: ( ) = > _confirmAction (
onTap: ( ) = > _confirmAction (
' Reboot Repeater ' ,
l10n . repeater_rebootRepeater ,
' Are you sure you want to reboot this repeater? ' ,
l10n . repeater_rebootRepeaterConfirm ,
( ) = > _sendDangerCommand ( ' reboot ' ) ,
( ) = > _sendDangerCommand ( ' reboot ' ) ,
) ,
) ,
) ,
) ,
@ -1235,14 +1240,14 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
/ / ) ,
/ / ) ,
ListTile (
ListTile (
leading: Icon ( Icons . delete_forever , color: colorScheme . onErrorContainer ) ,
leading: Icon ( Icons . delete_forever , color: colorScheme . onErrorContainer ) ,
title: Text ( ' Erase File System ' , style: TextStyle ( color: colorScheme . onErrorContainer ) ) ,
title: Text ( l10n . repeater_eraseFileSystem , style: TextStyle ( color: colorScheme . onErrorContainer ) ) ,
subtitle: Text (
subtitle: Text (
' Format the repeater file system ' ,
l10n . repeater_eraseFileSystemSubtitle ,
style: TextStyle ( color: colorScheme . onErrorContainer . withValues ( alpha: 0.8 ) ) ,
style: TextStyle ( color: colorScheme . onErrorContainer . withValues ( alpha: 0.8 ) ) ,
) ,
) ,
onTap: ( ) = > _confirmAction (
onTap: ( ) = > _confirmAction (
' Erase File System ' ,
l10n . repeater_eraseFileSystem ,
' WARNING: This will erase all data on the repeater. This cannot be undone! ' ,
l10n . repeater_eraseFileSystemConfirm ,
( ) = > _sendDangerCommand ( ' erase ' ) ,
( ) = > _sendDangerCommand ( ' erase ' ) ,
isDestructive: true ,
isDestructive: true ,
) ,
) ,
@ -1254,13 +1259,14 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
}
}
Future < void > _sendDangerCommand ( String command ) async {
Future < void > _sendDangerCommand ( String command ) async {
final l10n = context . l10n ;
final connector = Provider . of < MeshCoreConnector > ( context , listen: false ) ;
final connector = Provider . of < MeshCoreConnector > ( context , listen: false ) ;
final repeater = _resolveRepeater ( connector ) ;
final repeater = _resolveRepeater ( connector ) ;
if ( command = = ' erase ' ) {
if ( command = = ' erase ' ) {
if ( mounted ) {
if ( mounted ) {
ScaffoldMessenger . of ( context ) . showSnackBar (
ScaffoldMessenger . of ( context ) . showSnackBar (
const SnackBar ( content: Text ( ' Erase is only available over serial console. ' ) ) ,
SnackBar ( content: Text ( l10n . repeater_eraseSerialOnly ) ) ,
) ;
) ;
}
}
return ;
return ;
@ -1284,14 +1290,14 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
if ( mounted ) {
if ( mounted ) {
ScaffoldMessenger . of ( context ) . showSnackBar (
ScaffoldMessenger . of ( context ) . showSnackBar (
SnackBar ( content: Text ( ' Command sent: $ command ' ) ) ,
SnackBar ( content: Text ( l10n . repeater_commandSent ( command ) ) ) ,
) ;
) ;
}
}
} catch ( e ) {
} catch ( e ) {
if ( mounted ) {
if ( mounted ) {
ScaffoldMessenger . of ( context ) . showSnackBar (
ScaffoldMessenger . of ( context ) . showSnackBar (
SnackBar (
SnackBar (
content: Text ( ' Error sending command: $ e ' ) ,
content: Text ( l10n . repeater_errorSendingCommand ( e . toString ( ) ) ) ,
backgroundColor: Colors . red ,
backgroundColor: Colors . red ,
) ,
) ,
) ;
) ;
@ -1305,6 +1311,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
VoidCallback onConfirm , {
VoidCallback onConfirm , {
bool isDestructive = false ,
bool isDestructive = false ,
} ) {
} ) {
final l10n = context . l10n ;
showDialog (
showDialog (
context: context ,
context: context ,
builder: ( context ) = > AlertDialog (
builder: ( context ) = > AlertDialog (
@ -1313,7 +1320,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
actions: [
actions: [
TextButton (
TextButton (
onPressed: ( ) = > Navigator . pop ( context ) ,
onPressed: ( ) = > Navigator . pop ( context ) ,
child: const Text ( ' Cancel ' ) ,
child: Text ( l10n . common_cancel ) ,
) ,
) ,
FilledButton (
FilledButton (
onPressed: ( ) {
onPressed: ( ) {
@ -1323,7 +1330,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
style: isDestructive
style: isDestructive
? FilledButton . styleFrom ( backgroundColor: Colors . red )
? FilledButton . styleFrom ( backgroundColor: Colors . red )
: null ,
: null ,
child: const Text ( ' Confirm ' ) ,
child: Text ( l10n . repeater_confirm ) ,
) ,
) ,
] ,
] ,
) ,
) ,