feat(#351): reach contact settings from the long-press menu
The per-contact settings dialog (compression + telemetry grants) was reachable only from a contact's chat via ellipsis -> Contact settings. Extract it to a shared showContactSettingsDialog() and add a "Contact settings" entry as the first item of the contact long-press/right-click menu, so it opens the same surface from both places. chat_screen's _showContactSettings now delegates to the shared dialog; its _buildInfoRow stays (still used by the contact-info dialog). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/351-contact-settings-menu
parent
615c0ed3b5
commit
837984cf2a
@ -0,0 +1,180 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
|
||||
/// The per-contact settings dialog (Smaz/Cyr2Lat compression + telemetry
|
||||
/// grants). Shared so it can be opened from the chat ellipsis AND the contacts
|
||||
/// long-press menu without duplicating the body (#351).
|
||||
void showContactSettingsDialog(BuildContext context, Contact contact) {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
final appSettingsService = Provider.of<AppSettingsService>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
connector.ensureContactSmazSettingLoaded(contact.publicKeyHex);
|
||||
connector.ensureContactCyr2LatSettingLoaded(contact.publicKeyHex);
|
||||
bool smazEnabled = connector.isContactSmazEnabled(contact.publicKeyHex);
|
||||
bool cyr2latEnabled = connector.isContactCyr2LatEnabled(contact.publicKeyHex);
|
||||
String? selectedCyr2LatProfileId = connector.getContactCyr2LatProfileId(
|
||||
contact.publicKeyHex,
|
||||
);
|
||||
bool teleBaseEnabled = contact.teleBaseEnabled;
|
||||
bool teleLocEnabled = contact.teleLocEnabled;
|
||||
bool teleEnvEnabled = contact.teleEnvEnabled;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setDialogState) => AlertDialog(
|
||||
title: Text(context.l10n.contact_settings),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (contact.hasLocation) ...[
|
||||
_infoRow(
|
||||
context.l10n.chat_location,
|
||||
'${contact.latitude?.toStringAsFixed(4)}, ${contact.longitude?.toStringAsFixed(4)}',
|
||||
),
|
||||
const Divider(height: 8),
|
||||
],
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(context.l10n.channels_smazCompression),
|
||||
subtitle: Text(context.l10n.chat_compressOutgoingMessages),
|
||||
value: smazEnabled,
|
||||
onChanged: (value) {
|
||||
connector.setContactSmazEnabled(contact.publicKeyHex, value);
|
||||
connector.setContactCyr2LatEnabled(
|
||||
contact.publicKeyHex,
|
||||
false,
|
||||
);
|
||||
setDialogState(() {
|
||||
smazEnabled = value;
|
||||
if (smazEnabled) {
|
||||
cyr2latEnabled = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
const Divider(height: 8),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(context.l10n.channels_cyr2latCompression),
|
||||
subtitle: Text(context.l10n.channels_cyr2latCompressionDscr),
|
||||
value: cyr2latEnabled,
|
||||
onChanged: (value) {
|
||||
connector.setContactCyr2LatEnabled(
|
||||
contact.publicKeyHex,
|
||||
value,
|
||||
);
|
||||
connector.setContactSmazEnabled(contact.publicKeyHex, false);
|
||||
setDialogState(() {
|
||||
cyr2latEnabled = value;
|
||||
if (cyr2latEnabled) {
|
||||
smazEnabled = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
if (cyr2latEnabled) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 8, 0, 8),
|
||||
child: DropdownButtonFormField<String>(
|
||||
initialValue: selectedCyr2LatProfileId,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
context.l10n.channels_cyr2latSettingsSubheading,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: appSettingsService.settings.cyr2latProfiles.map((
|
||||
profile,
|
||||
) {
|
||||
return DropdownMenuItem(
|
||||
value: profile.id,
|
||||
child: Text(profile.name),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
connector.setContactCyr2LatProfileId(
|
||||
contact.publicKeyHex,
|
||||
value,
|
||||
);
|
||||
setDialogState(() {
|
||||
selectedCyr2LatProfileId = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
const Divider(height: 8),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(context.l10n.contact_teleBase),
|
||||
subtitle: Text(context.l10n.contact_teleBaseSubtitle),
|
||||
value: teleBaseEnabled,
|
||||
onChanged: (value) {
|
||||
setDialogState(() => teleBaseEnabled = value);
|
||||
},
|
||||
),
|
||||
const Divider(height: 8),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(context.l10n.contact_teleLoc),
|
||||
subtitle: Text(context.l10n.contact_teleLocSubtitle),
|
||||
value: teleLocEnabled,
|
||||
onChanged: (value) {
|
||||
setDialogState(() => teleLocEnabled = value);
|
||||
},
|
||||
),
|
||||
const Divider(height: 8),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(context.l10n.contact_teleEnv),
|
||||
subtitle: Text(context.l10n.contact_teleEnvSubtitle),
|
||||
value: teleEnvEnabled,
|
||||
onChanged: (value) {
|
||||
setDialogState(() => teleEnvEnabled = value);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
connector.setContactFlags(
|
||||
contact,
|
||||
teleBase: teleBaseEnabled,
|
||||
teleLoc: teleLocEnabled,
|
||||
teleEnv: teleEnvEnabled,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(context.l10n.common_close),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _infoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Text(label, style: TextStyle(color: Colors.grey[600])),
|
||||
),
|
||||
Expanded(child: SelectableText(value)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Loading…
Reference in new issue