feat(presence): presence-mode selector in Settings (W7 P5)
Add a 'Presence' section to Settings with three radio options wired to the persisted PresenceMode (W7): Normal (friends only, no beacon; default), Invisible (appear offline to everyone), Discoverable (also publish so friends can find you after a network change). Each has a hover tooltip explainer, mirroring the recording-mode radios. Selecting one saves config.presence_mode. The live friends listener (P4, not yet wired) will read this posture when it lands; no core command until then. 256 lib tests green, clippy clean, release builds. Solo screenshot-verifiable (the live effect needs P4 + 2 machines). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ use crate::network::PeerState;
|
||||
use crate::notify::{self, Sound};
|
||||
use crate::audio::pw_cli::{AudioDevice, enumerate_audio_devices};
|
||||
use crate::config::{AppConfig, NetworkMode, RecordingMode, RoomLayout};
|
||||
use crate::presence::PresenceMode;
|
||||
use crate::theme::{AppTheme, Palette};
|
||||
|
||||
use iced::widget::{
|
||||
@@ -135,6 +136,8 @@ pub enum AppMessage {
|
||||
NoiseGateDragging(f32),
|
||||
NetworkModeSelected(NetworkMode),
|
||||
RecordingModeSelected(RecordingMode),
|
||||
/// Choose the friends presence posture (W7): invisible / normal / discoverable.
|
||||
PresenceModeSelected(PresenceMode),
|
||||
EventOccurred(Event),
|
||||
NavigateToSettings,
|
||||
NavigateBack,
|
||||
@@ -733,6 +736,12 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
|
||||
// Takes effect on the next recording start.
|
||||
let _ = state.controller.send(CoreCommand::SetRecordingMode(mode));
|
||||
}
|
||||
AppMessage::PresenceModeSelected(mode) => {
|
||||
state.config.presence_mode = mode;
|
||||
state.config.save();
|
||||
// Persisted now; the live friends listener (P4, not yet wired) reads
|
||||
// this posture when it lands. No core command until then.
|
||||
}
|
||||
AppMessage::ToggleNotifications(enabled) => {
|
||||
state.config.notifications_enabled = enabled;
|
||||
state.config.save();
|
||||
@@ -1009,6 +1018,21 @@ fn recording_mode_hint(mode: RecordingMode) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// One-line explanation of a presence posture for the settings picker (W7).
|
||||
fn presence_mode_hint(mode: PresenceMode) -> &'static str {
|
||||
match mode {
|
||||
PresenceMode::Invisible => {
|
||||
"Answer no one — appear offline to everyone, even friends."
|
||||
}
|
||||
PresenceMode::Normal => {
|
||||
"Answer friends only, from your saved address. No presence beacon."
|
||||
}
|
||||
PresenceMode::Discoverable => {
|
||||
"Also publish so friends can still find you after you change networks (opt-in)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats a call duration as `m:ss` (or `h:mm:ss` past an hour).
|
||||
fn format_duration(total_secs: u64) -> String {
|
||||
let h = total_secs / 3600;
|
||||
@@ -1475,6 +1499,30 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
identity_warning,
|
||||
].spacing(8).width(iced::Length::Fill);
|
||||
|
||||
// --- Presence (W7) — how reachable you are to friends while idle. ---
|
||||
let presence_radio = |mode: PresenceMode, label: &'static str| -> Element<'_, AppMessage> {
|
||||
tooltip(
|
||||
radio(label, mode, Some(state.config.presence_mode), AppMessage::PresenceModeSelected),
|
||||
container(text(presence_mode_hint(mode)).size(11).color(color_text))
|
||||
.padding(8)
|
||||
.max_width(320.0)
|
||||
.style(c_style(color_crust, color_surface, 6.0)),
|
||||
iced::widget::tooltip::Position::Right,
|
||||
)
|
||||
.gap(8)
|
||||
.into()
|
||||
};
|
||||
let presence_section = column![
|
||||
text("Controls whether friends can see you're online (and what room \
|
||||
you're in) while the app is open. Discovery stays off unless you \
|
||||
pick Discoverable.")
|
||||
.size(12)
|
||||
.color(color_subtext),
|
||||
presence_radio(PresenceMode::Normal, "Normal — friends only, no beacon"),
|
||||
presence_radio(PresenceMode::Invisible, "Invisible — appear offline"),
|
||||
presence_radio(PresenceMode::Discoverable, "Discoverable — findable after a network change"),
|
||||
].spacing(8).width(iced::Length::Fill);
|
||||
|
||||
let settings_content = scrollable(
|
||||
column![
|
||||
// --- Audio Devices ---
|
||||
@@ -1571,6 +1619,11 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
identity_section,
|
||||
vertical_space(section_gap),
|
||||
|
||||
// --- Presence ---
|
||||
section_header("Presence"),
|
||||
presence_section,
|
||||
vertical_space(section_gap),
|
||||
|
||||
// --- Notifications & Sounds ---
|
||||
section_header("Notifications & Sounds"),
|
||||
column![
|
||||
|
||||
Reference in New Issue
Block a user