Connected Devices: fixed UI settings. fixed pairing protocol

This commit is contained in:
Ultradesu
2026-07-24 02:21:24 +03:00
parent fa58b2ce43
commit e08eb71ab7
12 changed files with 869 additions and 177 deletions
+63 -15
View File
@@ -67,48 +67,70 @@ fn draw_settings_rows(frame: &mut Frame, area: Rect, state: &AppState) {
}
y = y.saturating_add(1);
let connected_devices_enabled = state.connected_devices_enabled();
draw_section(frame, area, &mut y, "Connected Devices");
let disabled_value = "enable federation first".to_string();
let devices = state.federation.devices.as_ref();
draw_row(
draw_row_enabled(
frame,
area,
&mut y,
cursor,
state.settings_cursor,
"This device name",
devices
.map(|status| status.this_device_name.clone())
.unwrap_or_else(|| "loading…".to_string()),
if connected_devices_enabled {
devices
.map(|status| status.this_device_name.clone())
.unwrap_or_else(|| "loading…".to_string())
} else {
disabled_value.clone()
},
connected_devices_enabled,
);
cursor += 1;
draw_row(
draw_row_enabled(
frame,
area,
&mut y,
cursor,
state.settings_cursor,
"Generate device invite",
"".to_string(),
if connected_devices_enabled {
"".to_string()
} else {
disabled_value.clone()
},
connected_devices_enabled,
);
cursor += 1;
draw_row(
draw_row_enabled(
frame,
area,
&mut y,
cursor,
state.settings_cursor,
"Connect device by invite…",
"".to_string(),
if connected_devices_enabled {
"".to_string()
} else {
disabled_value.clone()
},
connected_devices_enabled,
);
cursor += 1;
draw_row(
draw_row_enabled(
frame,
area,
&mut y,
cursor,
state.settings_cursor,
"Sync devices now",
"".to_string(),
if connected_devices_enabled {
"".to_string()
} else {
disabled_value.clone()
},
connected_devices_enabled,
);
cursor += 1;
if let Some(status) = devices {
@@ -125,12 +147,15 @@ fn draw_settings_rows(frame: &mut Frame, area: Rect, state: &AppState) {
} else {
format!("v{}", device.client_version)
};
let value = if device.is_self || device.revoked {
let can_revoke = connected_devices_enabled && !device.is_self && !device.revoked;
let value = if can_revoke {
format!("{version} · revoke ↵")
} else if connected_devices_enabled {
version
} else {
format!("{version} · revoke ↵")
disabled_value.clone()
};
draw_row(
draw_row_enabled(
frame,
area,
&mut y,
@@ -138,6 +163,7 @@ fn draw_settings_rows(frame: &mut Frame, area: Rect, state: &AppState) {
state.settings_cursor,
&label,
value,
connected_devices_enabled,
);
cursor += 1;
}
@@ -234,6 +260,19 @@ fn draw_row(
cursor: usize,
label: &str,
value: String,
) {
draw_row_enabled(frame, area, y, row_index, cursor, label, value, true);
}
fn draw_row_enabled(
frame: &mut Frame,
area: Rect,
y: &mut u16,
row_index: usize,
cursor: usize,
label: &str,
value: String,
enabled: bool,
) {
if *y >= area.y + area.height {
return;
@@ -248,10 +287,19 @@ fn draw_row(
let marker = if selected { "" } else { " " };
let label_width = 48usize;
let line = Line::from(vec![
Span::styled(marker, theme::accent()),
Span::styled(
marker,
if enabled {
theme::accent()
} else {
theme::dim()
},
),
Span::styled(
format!("{label:<label_width$}"),
if selected {
if !enabled {
theme::dim()
} else if selected {
theme::accent()
} else {
ratatui::style::Style::default()
+3 -9
View File
@@ -847,7 +847,7 @@ fn draw_search(frame: &mut Frame, area: Rect, state: &AppState, cursor: usize) {
index += 1;
}
for fed in &state.search.fed_tracks {
let heart = if state.fed_likes.contains(&fed.item_id) {
let heart = if state.fed_track_liked(fed) {
Span::styled("", theme::accent())
} else {
Span::raw(" ")
@@ -1090,10 +1090,7 @@ fn draw_fed_appearance_row(
visual_selected: bool,
) {
let track = &appearance.track;
let liked = track
.sources
.iter()
.any(|(_, item_id)| state.fed_likes.contains(item_id));
let liked = state.fed_card_track_liked(track);
let heart = if liked {
Span::styled("", theme::accent())
} else {
@@ -1276,10 +1273,7 @@ fn draw_fed_release(frame: &mut Frame, area: Rect, state: &AppState, index: usiz
duration
};
let in_selection = state.track_selection.contains(&scope, position);
let liked = track
.sources
.iter()
.any(|(_, item_id)| state.fed_likes.contains(item_id));
let liked = state.fed_card_track_liked(track);
let heart = if liked {
Span::styled("", theme::accent())
} else {
+1 -1
View File
@@ -71,7 +71,7 @@ pub(crate) fn track_row(
let fed_liked = track
.fed
.as_ref()
.is_some_and(|fed| state.fed_likes.contains(&fed.item_id));
.is_some_and(|fed| state.fed_track_liked(fed));
let heart = if state.likes.contains(&track.id) || fed_liked {
Span::styled("", theme::accent())
} else {
+59 -6
View File
@@ -40,8 +40,17 @@ pub fn draw(frame: &mut Frame, state: &AppState) {
device_id,
name,
client_version,
requester_group_id,
requester_group_active_devices,
..
}) => draw_device_pairing(frame, device_id, name, client_version),
}) => draw_device_pairing(
frame,
device_id,
name,
client_version,
requester_group_id.as_deref(),
*requester_group_active_devices,
),
Some(Popup::ConfirmDeviceRevoke { device_id, name }) => {
draw_device_revoke(frame, device_id, name)
}
@@ -136,8 +145,20 @@ fn draw_fed_text(frame: &mut Frame, title: &str, text: &str) {
);
}
fn draw_device_pairing(frame: &mut Frame, device_id: &str, name: &str, client_version: &str) {
let area = centered(frame.area(), 64, 8);
fn draw_device_pairing(
frame: &mut Frame,
device_id: &str,
name: &str,
client_version: &str,
requester_group_id: Option<&str>,
requester_group_active_devices: usize,
) {
let group_conflict = requester_group_id.is_some() && requester_group_active_devices > 1;
let area = centered(
frame.area(),
if group_conflict { 76 } else { 64 },
if group_conflict { 12 } else { 8 },
);
let block = Block::bordered()
.title(" Pair device ")
.title_style(theme::header())
@@ -145,7 +166,7 @@ fn draw_device_pairing(frame: &mut Frame, device_id: &str, name: &str, client_ve
let inner = block.inner(area);
frame.render_widget(Clear, area);
frame.render_widget(block, area);
let lines = vec![
let mut lines = vec![
Line::from(vec![
Span::styled("Name ", theme::dim()),
Span::raw(name.to_string()),
@@ -158,9 +179,41 @@ fn draw_device_pairing(frame: &mut Frame, device_id: &str, name: &str, client_ve
Span::styled("Device ", theme::dim()),
Span::raw(device_id.chars().take(24).collect::<String>()),
]),
Line::default(),
Line::styled("y accept · n/esc deny", theme::dim()),
];
if let Some(group_id) = requester_group_id.filter(|_| group_conflict) {
lines.extend([
Line::from(vec![
Span::styled("Group ", theme::dim()),
Span::raw(format!(
"{} · {requester_group_active_devices} active devices",
group_id.chars().take(24).collect::<String>()
)),
]),
Line::default(),
Line::styled(
"Recommended joins that group and keeps its peers syncing.",
theme::dim(),
),
Line::styled(
"Cancel keeps this group; that device will switch groups and its peers may stop syncing.",
theme::dim(),
),
Line::default(),
Line::from(vec![
Span::styled(" Recommended ", theme::tab_active()),
Span::raw(" "),
Span::styled(" Cancel ", theme::danger_button()),
])
.alignment(Alignment::Center),
Line::styled("y recommended · c cancel · n/esc deny", theme::dim())
.alignment(Alignment::Center),
]);
} else {
lines.extend([
Line::default(),
Line::styled("y accept · n/esc deny", theme::dim()),
]);
}
frame.render_widget(Paragraph::new(lines), inner);
}