mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-26 04:12:57 +10:00
Merge branch 'dev' of https://github.com/zjs81/meshcore-open into dev
This commit is contained in:
@@ -384,7 +384,6 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
||||
IconData icon;
|
||||
Color iconColor;
|
||||
Color bgColor;
|
||||
String subtitle;
|
||||
|
||||
if (isCommunityChannel) {
|
||||
// Community channel styling
|
||||
@@ -392,28 +391,21 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
||||
bgColor = Colors.purple.withValues(alpha: 0.2);
|
||||
if (isCommunityPublic) {
|
||||
icon = Icons.groups;
|
||||
subtitle =
|
||||
'${context.l10n.community_publicChannel} • ${community.name}';
|
||||
} else {
|
||||
icon = Icons.tag;
|
||||
subtitle =
|
||||
'${context.l10n.community_hashtagChannel} • ${community.name}';
|
||||
}
|
||||
} else if (channel.isPublicChannel) {
|
||||
icon = Icons.public;
|
||||
iconColor = Colors.green;
|
||||
bgColor = Colors.green.withValues(alpha: 0.2);
|
||||
subtitle = context.l10n.channels_publicChannel;
|
||||
} else if (channel.name.startsWith('#')) {
|
||||
icon = Icons.tag;
|
||||
iconColor = Colors.blue;
|
||||
bgColor = Colors.blue.withValues(alpha: 0.2);
|
||||
subtitle = context.l10n.channels_hashtagChannel;
|
||||
} else {
|
||||
icon = Icons.lock;
|
||||
iconColor = Colors.blue;
|
||||
bgColor = Colors.blue.withValues(alpha: 0.2);
|
||||
subtitle = context.l10n.channels_privateChannel;
|
||||
}
|
||||
|
||||
return Card(
|
||||
@@ -430,14 +422,17 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
||||
: null,
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
minVerticalPadding: 0,
|
||||
minVerticalPadding: 14,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
visualDensity: const VisualDensity(vertical: -2),
|
||||
leading: Stack(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: bgColor,
|
||||
child: Icon(icon, color: iconColor),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||
child: CircleAvatar(
|
||||
backgroundColor: bgColor,
|
||||
child: Icon(icon, color: iconColor),
|
||||
),
|
||||
),
|
||||
if (isCommunityChannel)
|
||||
Positioned(
|
||||
@@ -469,11 +464,6 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
||||
: channel.name,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text(
|
||||
subtitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
||||
@@ -2011,9 +2011,6 @@ class _MapScreenState extends State<MapScreen> {
|
||||
color: isPublic ? Colors.orange : Colors.blue,
|
||||
),
|
||||
title: Text(label),
|
||||
subtitle: isPublic
|
||||
? Text(context.l10n.channels_publicChannel)
|
||||
: null,
|
||||
onTap: () async {
|
||||
Navigator.pop(sheetContext);
|
||||
final canSend = isPublic
|
||||
|
||||
@@ -585,6 +585,62 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
command: 'clear stats',
|
||||
description: l10n.repeater_cliHelpClearStats,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'poweroff',
|
||||
description: l10n.repeater_cliHelpPowerOff,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'shutdown',
|
||||
description: l10n.repeater_cliHelpPowerOff,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'clkreboot',
|
||||
description: l10n.repeater_cliHelpClkReboot,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'advert.zerohop',
|
||||
description: l10n.repeater_cliHelpAdvertZeroHop,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'start ota',
|
||||
description: l10n.repeater_cliHelpStartOta,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'time {epoch-seconds}',
|
||||
description: l10n.repeater_cliHelpTime,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'board',
|
||||
description: l10n.repeater_cliHelpBoard,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'discover.neighbors',
|
||||
description: l10n.repeater_cliHelpDiscoverNeighbors,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'powersaving',
|
||||
description: l10n.repeater_cliHelpPowersaving,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'powersaving {on|off}',
|
||||
description: l10n.repeater_cliHelpPowersavingOnOff,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'erase',
|
||||
description: l10n.repeater_cliHelpErase,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'stats-packets',
|
||||
description: l10n.repeater_cliHelpStatsPackets,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'stats-radio',
|
||||
description: l10n.repeater_cliHelpStatsRadio,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'stats-core',
|
||||
description: l10n.repeater_cliHelpStatsCore,
|
||||
),
|
||||
];
|
||||
|
||||
final settingsCommands = [
|
||||
@@ -692,6 +748,38 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
command: 'setperm {pubkey-hex} {permissions}',
|
||||
description: l10n.repeater_cliHelpSetPerm,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'set dutycycle {1-100}',
|
||||
description: l10n.repeater_cliHelpSetDutyCycle,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'set prv.key {hex}',
|
||||
description: l10n.repeater_cliHelpSetPrvKey,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'set radio.rxgain {on|off}',
|
||||
description: l10n.repeater_cliHelpSetRadioRxGain,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'set owner.info {text}',
|
||||
description: l10n.repeater_cliHelpSetOwnerInfo,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'set path.hash.mode {0|1|2}',
|
||||
description: l10n.repeater_cliHelpSetPathHashMode,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'set loop.detect {off|minimal|moderate|strict}',
|
||||
description: l10n.repeater_cliHelpSetLoopDetect,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'set freq {mhz}',
|
||||
description: l10n.repeater_cliHelpSetFreq,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'set bridge.channel {1-14}',
|
||||
description: l10n.repeater_cliHelpSetBridgeChannel,
|
||||
),
|
||||
];
|
||||
|
||||
final bridgeCommands = [
|
||||
@@ -768,6 +856,203 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
command: 'region save',
|
||||
description: l10n.repeater_cliHelpRegionSave,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'region default',
|
||||
description: l10n.repeater_cliHelpRegionDefault,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'region default {* | name-prefix | <null>}',
|
||||
description: l10n.repeater_cliHelpRegionDefaultSet,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'region list allowed',
|
||||
description: l10n.repeater_cliHelpRegionListAllowed,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'region list denied',
|
||||
description: l10n.repeater_cliHelpRegionListDenied,
|
||||
),
|
||||
];
|
||||
|
||||
final getCommands = [
|
||||
_CommandHelpEntry(
|
||||
command: 'get name',
|
||||
description: l10n.repeater_cliHelpGetName,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get role',
|
||||
description: l10n.repeater_cliHelpGetRole,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get public.key',
|
||||
description: l10n.repeater_cliHelpGetPublicKey,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get prv.key',
|
||||
description: l10n.repeater_cliHelpGetPrvKey,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get repeat',
|
||||
description: l10n.repeater_cliHelpGetRepeat,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get tx',
|
||||
description: l10n.repeater_cliHelpGetTx,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get freq',
|
||||
description: l10n.repeater_cliHelpGetFreq,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get radio',
|
||||
description: l10n.repeater_cliHelpGetRadio,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get radio.rxgain',
|
||||
description: l10n.repeater_cliHelpGetRadioRxGain,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get af',
|
||||
description: l10n.repeater_cliHelpGetAf,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get dutycycle',
|
||||
description: l10n.repeater_cliHelpGetDutyCycle,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get int.thresh',
|
||||
description: l10n.repeater_cliHelpGetIntThresh,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get agc.reset.interval',
|
||||
description: l10n.repeater_cliHelpGetAgcResetInterval,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get multi.acks',
|
||||
description: l10n.repeater_cliHelpGetMultiAcks,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get allow.read.only',
|
||||
description: l10n.repeater_cliHelpGetAllowReadOnly,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get advert.interval',
|
||||
description: l10n.repeater_cliHelpGetAdvertInterval,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get flood.advert.interval',
|
||||
description: l10n.repeater_cliHelpGetFloodAdvertInterval,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get guest.password',
|
||||
description: l10n.repeater_cliHelpGetGuestPassword,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get lat',
|
||||
description: l10n.repeater_cliHelpGetLat,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get lon',
|
||||
description: l10n.repeater_cliHelpGetLon,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get rxdelay',
|
||||
description: l10n.repeater_cliHelpGetRxDelay,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get txdelay',
|
||||
description: l10n.repeater_cliHelpGetTxDelay,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get direct.txdelay',
|
||||
description: l10n.repeater_cliHelpGetDirectTxDelay,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get flood.max',
|
||||
description: l10n.repeater_cliHelpGetFloodMax,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get owner.info',
|
||||
description: l10n.repeater_cliHelpGetOwnerInfo,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get path.hash.mode',
|
||||
description: l10n.repeater_cliHelpGetPathHashMode,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get loop.detect',
|
||||
description: l10n.repeater_cliHelpGetLoopDetect,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get acl',
|
||||
description: l10n.repeater_cliHelpGetAcl,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get bridge.enabled',
|
||||
description: l10n.repeater_cliHelpGetBridgeEnabled,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get bridge.delay',
|
||||
description: l10n.repeater_cliHelpGetBridgeDelay,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get bridge.source',
|
||||
description: l10n.repeater_cliHelpGetBridgeSource,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get bridge.baud',
|
||||
description: l10n.repeater_cliHelpGetBridgeBaud,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get bridge.channel',
|
||||
description: l10n.repeater_cliHelpGetBridgeChannel,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get bridge.secret',
|
||||
description: l10n.repeater_cliHelpGetBridgeSecret,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get bootloader.ver',
|
||||
description: l10n.repeater_cliHelpGetBootloaderVer,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get adc.multiplier',
|
||||
description: l10n.repeater_cliHelpGetAdcMultiplier,
|
||||
),
|
||||
];
|
||||
|
||||
final powerMgmtCommands = [
|
||||
_CommandHelpEntry(
|
||||
command: 'get pwrmgt.support',
|
||||
description: l10n.repeater_cliHelpGetPwrMgtSupport,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get pwrmgt.source',
|
||||
description: l10n.repeater_cliHelpGetPwrMgtSource,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get pwrmgt.bootreason',
|
||||
description: l10n.repeater_cliHelpGetPwrMgtBootReason,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'get pwrmgt.bootmv',
|
||||
description: l10n.repeater_cliHelpGetPwrMgtBootMv,
|
||||
),
|
||||
];
|
||||
|
||||
final sensorCommands = [
|
||||
_CommandHelpEntry(
|
||||
command: 'sensor get {key}',
|
||||
description: l10n.repeater_cliHelpSensorGet,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'sensor set {key} {value}',
|
||||
description: l10n.repeater_cliHelpSensorSet,
|
||||
),
|
||||
_CommandHelpEntry(
|
||||
command: 'sensor list [start]',
|
||||
description: l10n.repeater_cliHelpSensorList,
|
||||
),
|
||||
];
|
||||
|
||||
final gpsCommands = [
|
||||
@@ -814,12 +1099,26 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
||||
generalCommands,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildHelpSection(
|
||||
context,
|
||||
l10n.repeater_getCategory,
|
||||
getCommands,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildHelpSection(
|
||||
context,
|
||||
l10n.repeater_settingsCategory,
|
||||
settingsCommands,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildHelpSection(
|
||||
context,
|
||||
l10n.repeater_powerMgmt,
|
||||
powerMgmtCommands,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildHelpSection(context, l10n.repeater_sensors, sensorCommands),
|
||||
const SizedBox(height: 16),
|
||||
_buildHelpSection(context, l10n.repeater_bridge, bridgeCommands),
|
||||
const SizedBox(height: 16),
|
||||
_buildHelpSection(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,6 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
StreamSubscription<Uint8List>? _frameSubscription;
|
||||
RepeaterCommandService? _commandService;
|
||||
Timer? _statusTimeout;
|
||||
DateTime? _statusRequestedAt;
|
||||
int? _batteryMv;
|
||||
int? _uptimeSecs;
|
||||
int? _queueLen;
|
||||
@@ -56,6 +55,7 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
int? _directRx;
|
||||
int? _dupFlood;
|
||||
int? _dupDirect;
|
||||
double? _chanUtil;
|
||||
PathSelection? _pendingStatusSelection;
|
||||
|
||||
@override
|
||||
@@ -64,7 +64,11 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
_commandService = RepeaterCommandService(connector);
|
||||
_setupMessageListener();
|
||||
_loadStatus();
|
||||
// Defer until after the first frame so any notifyListeners() triggered
|
||||
// during preparePathForContactSend doesn't fire mid-build.
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _loadStatus();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -192,6 +196,7 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
_lastSnr = lastSnrRaw / 4.0;
|
||||
_dupDirect = directDups;
|
||||
_dupFlood = floodDups;
|
||||
_chanUtil = ((txAirSecs + rxAirSecs) / uptimeSecs) * 100;
|
||||
});
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
connector.updateRepeaterBatterySnapshot(
|
||||
@@ -264,7 +269,6 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_statusRequestedAt = DateTime.now();
|
||||
_pendingStatusSelection = null;
|
||||
_batteryMv = null;
|
||||
_uptimeSecs = null;
|
||||
@@ -283,6 +287,7 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
_directRx = null;
|
||||
_dupFlood = null;
|
||||
_dupDirect = null;
|
||||
_chanUtil = null;
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -570,6 +575,7 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
_buildInfoRow(l10n.repeater_sent, _packetTxText()),
|
||||
_buildInfoRow(l10n.repeater_received, _packetRxText()),
|
||||
_buildInfoRow(l10n.repeater_duplicates, _duplicateText()),
|
||||
_buildInfoRow(l10n.repeater_chanUtil, _chanUtilText()),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -639,11 +645,13 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
}
|
||||
|
||||
String _clockText() {
|
||||
if (_statusRequestedAt == null) return '—';
|
||||
final dt = _statusRequestedAt!;
|
||||
final date = '${dt.day}/${dt.month}/${dt.year}';
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
final dt = connector.repeaterClockAtLogin(widget.repeater.publicKey);
|
||||
if (dt == null) return '—';
|
||||
final local = dt.toLocal();
|
||||
final date = '${local.day}/${local.month}/${local.year}';
|
||||
final time =
|
||||
'${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
|
||||
'${local.hour.toString().padLeft(2, '0')}:${local.minute.toString().padLeft(2, '0')}';
|
||||
return '$date $time';
|
||||
}
|
||||
|
||||
@@ -673,6 +681,11 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
return l10n.repeater_packetRxTotal(_packetsRecv!, flood, direct);
|
||||
}
|
||||
|
||||
String _chanUtilText() {
|
||||
if (_chanUtil == null) return '—';
|
||||
return _formatPercent(_chanUtil);
|
||||
}
|
||||
|
||||
String _duplicateText() {
|
||||
final l10n = context.l10n;
|
||||
if (_dupFlood != null || _dupDirect != null) {
|
||||
@@ -693,6 +706,11 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
|
||||
return suffix == null ? value.toString() : '$value$suffix';
|
||||
}
|
||||
|
||||
String _formatPercent(double? p) {
|
||||
if (p == null) return '—';
|
||||
return '${p.toStringAsFixed(2)}%';
|
||||
}
|
||||
|
||||
String _formatSnr(double? snr) {
|
||||
if (snr == null) return '—';
|
||||
return snr.toStringAsFixed(2);
|
||||
|
||||
@@ -305,6 +305,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () => _editLocation(context, connector),
|
||||
),
|
||||
if (connector.currentCustomVars?.containsKey('gps') ?? false) ...[
|
||||
const Divider(height: 1),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.gps_fixed),
|
||||
title: Text(l10n.settings_locationGPSEnable),
|
||||
subtitle: Text(l10n.settings_locationGPSEnableSubtitle),
|
||||
value: connector.currentCustomVars?['gps'] == '1',
|
||||
onChanged: (value) async {
|
||||
await connector.setCustomVar(value ? 'gps:1' : 'gps:0');
|
||||
},
|
||||
),
|
||||
],
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.group_add_outlined),
|
||||
@@ -405,8 +417,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.bluetooth_outlined),
|
||||
title: Text(l10n.settings_bleDebugLog),
|
||||
subtitle: Text(l10n.settings_bleDebugLogSubtitle),
|
||||
title: Text(l10n.settings_companionDebugLog),
|
||||
subtitle: Text(l10n.settings_companionDebugLogSubtitle),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
|
||||
Reference in New Issue
Block a user