refactor(w7): move recents into its own card on the home screen
A growing recents history shouldn't reflow the Connect card's Create/Join controls. Extract the rendering into a `recents_card` free fn (mirroring `friends_panel`'s self-contained styling) and place it in the left column beneath the Connect card — both are "get into a room" — with Friends on the right. The card is omitted entirely (no stray gap) when empty, in both the narrow (stacked) and wide (row) responsive layouts. Screenshot-verified at the default width: Connect + Recent Rooms stacked left, Friends right; the Connect card stays fixed-size as recents grow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+114
-56
@@ -1290,7 +1290,6 @@ fn connect_card(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
let color_subtext = pal.subtext;
|
let color_subtext = pal.subtext;
|
||||||
let color_blue = pal.blue;
|
let color_blue = pal.blue;
|
||||||
let color_lavender = pal.lavender;
|
let color_lavender = pal.lavender;
|
||||||
let color_maroon = pal.maroon;
|
|
||||||
|
|
||||||
let c_style = move |bg: Color, b_color: Color, radius: f32| {
|
let c_style = move |bg: Color, b_color: Color, radius: f32| {
|
||||||
move |_theme: &Theme| container::Style {
|
move |_theme: &Theme| container::Style {
|
||||||
@@ -1327,55 +1326,6 @@ fn connect_card(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
selection: color_blue,
|
selection: color_blue,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Recently-joined rooms (W7 P5): a one-click rejoin list. Only shown when
|
|
||||||
// non-empty so the launch card stays clean on a fresh install.
|
|
||||||
let recents_group: Element<AppMessage> = if state.config.recents.is_empty() {
|
|
||||||
column![].into()
|
|
||||||
} else {
|
|
||||||
let now = std::time::SystemTime::now()
|
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.map(|d| d.as_secs())
|
|
||||||
.unwrap_or(0);
|
|
||||||
let mut rows = column![].spacing(6).width(iced::Length::Fill);
|
|
||||||
for r in &state.config.recents {
|
|
||||||
let label = {
|
|
||||||
let n = crate::sanitize::sanitize_name(&r.name);
|
|
||||||
if n.is_empty() { "Untitled room".to_string() } else { n }
|
|
||||||
};
|
|
||||||
let when = crate::recents::relative_time(now, r.joined_at);
|
|
||||||
let entry = button(
|
|
||||||
row![
|
|
||||||
text(label).size(14).color(color_text),
|
|
||||||
horizontal_space(),
|
|
||||||
text(when).size(11).color(color_subtext),
|
|
||||||
]
|
|
||||||
.align_y(iced::alignment::Vertical::Center),
|
|
||||||
)
|
|
||||||
.on_press(AppMessage::JoinRecent(r.ticket.clone()))
|
|
||||||
.style(b_style(color_crust, color_surface, color_text, 6.0))
|
|
||||||
.padding(8)
|
|
||||||
.width(iced::Length::Fill);
|
|
||||||
rows = rows.push(
|
|
||||||
row![
|
|
||||||
entry,
|
|
||||||
button(text("✕").size(12))
|
|
||||||
.on_press(AppMessage::RemoveRecent(r.ticket.clone()))
|
|
||||||
.style(b_style(color_surface, color_maroon, color_text, 6.0))
|
|
||||||
.padding(8),
|
|
||||||
]
|
|
||||||
.spacing(6)
|
|
||||||
.align_y(iced::alignment::Vertical::Center),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
column![
|
|
||||||
text("Recent rooms").size(14).color(color_subtext),
|
|
||||||
vertical_space(4.0),
|
|
||||||
rows,
|
|
||||||
]
|
|
||||||
.width(iced::Length::Fill)
|
|
||||||
.into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let logo = text("PEERSPEAK").size(36).color(color_blue);
|
let logo = text("PEERSPEAK").size(36).color(color_blue);
|
||||||
let subtitle = text("NAT-traversing full-mesh voice chat").size(16).color(color_subtext);
|
let subtitle = text("NAT-traversing full-mesh voice chat").size(16).color(color_subtext);
|
||||||
|
|
||||||
@@ -1433,8 +1383,6 @@ fn connect_card(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
text("— OR —").size(12).color(color_surface).align_x(iced::alignment::Horizontal::Center),
|
text("— OR —").size(12).color(color_surface).align_x(iced::alignment::Horizontal::Center),
|
||||||
vertical_space(16.0),
|
vertical_space(16.0),
|
||||||
join_group,
|
join_group,
|
||||||
vertical_space(16.0),
|
|
||||||
recents_group,
|
|
||||||
vertical_space(10.0),
|
vertical_space(10.0),
|
||||||
status
|
status
|
||||||
]
|
]
|
||||||
@@ -1447,6 +1395,102 @@ fn connect_card(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The "Recent rooms" card (W7 P5): a one-click rejoin list, in its own card so a
|
||||||
|
/// growing history never reflows the Connect card's Create/Join controls. Returns
|
||||||
|
/// an empty element when there are no recents, so the home screen can omit it
|
||||||
|
/// entirely on a fresh install. Mirrors `friends_panel`'s self-contained styling.
|
||||||
|
fn recents_card(state: &AppState) -> Element<'_, AppMessage> {
|
||||||
|
if state.config.recents.is_empty() {
|
||||||
|
return column![].into();
|
||||||
|
}
|
||||||
|
let pal = state.config.theme.palette();
|
||||||
|
let color_crust = pal.crust;
|
||||||
|
let color_mantle = pal.mantle;
|
||||||
|
let color_surface = pal.surface;
|
||||||
|
let color_text = pal.text;
|
||||||
|
let color_subtext = pal.subtext;
|
||||||
|
let color_maroon = pal.maroon;
|
||||||
|
|
||||||
|
let c_style = move |bg: Color, b_color: Color, radius: f32| {
|
||||||
|
move |_theme: &Theme| container::Style {
|
||||||
|
text_color: Some(color_text),
|
||||||
|
background: Some(Background::Color(bg)),
|
||||||
|
border: Border {
|
||||||
|
color: b_color,
|
||||||
|
width: if b_color == Color::TRANSPARENT { 0.0 } else { 1.0 },
|
||||||
|
radius: radius.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let b_style = move |bg: Color, hover_bg: Color, text_c: Color, radius: f32| {
|
||||||
|
move |_theme: &Theme, status: button::Status| {
|
||||||
|
let active_bg = match status {
|
||||||
|
button::Status::Hovered => hover_bg,
|
||||||
|
_ => bg,
|
||||||
|
};
|
||||||
|
button::Style {
|
||||||
|
background: Some(Background::Color(active_bg)),
|
||||||
|
text_color: text_c,
|
||||||
|
border: Border { color: Color::TRANSPARENT, width: 0.0, radius: radius.into() },
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let now = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let mut rows = column![].spacing(6).width(iced::Length::Fill);
|
||||||
|
for r in &state.config.recents {
|
||||||
|
let label = {
|
||||||
|
let n = crate::sanitize::sanitize_name(&r.name);
|
||||||
|
if n.is_empty() { "Untitled room".to_string() } else { n }
|
||||||
|
};
|
||||||
|
let when = crate::recents::relative_time(now, r.joined_at);
|
||||||
|
let entry = button(
|
||||||
|
row![
|
||||||
|
text(label).size(14).color(color_text),
|
||||||
|
horizontal_space(),
|
||||||
|
text(when).size(11).color(color_subtext),
|
||||||
|
]
|
||||||
|
.align_y(iced::alignment::Vertical::Center),
|
||||||
|
)
|
||||||
|
.on_press(AppMessage::JoinRecent(r.ticket.clone()))
|
||||||
|
.style(b_style(color_crust, color_surface, color_text, 6.0))
|
||||||
|
.padding(8)
|
||||||
|
.width(iced::Length::Fill);
|
||||||
|
rows = rows.push(
|
||||||
|
row![
|
||||||
|
entry,
|
||||||
|
button(text("✕").size(12))
|
||||||
|
.on_press(AppMessage::RemoveRecent(r.ticket.clone()))
|
||||||
|
.style(b_style(color_surface, color_maroon, color_text, 6.0))
|
||||||
|
.padding(8),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.align_y(iced::alignment::Vertical::Center),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
container(
|
||||||
|
column![
|
||||||
|
text("RECENT ROOMS").size(18).color(color_text),
|
||||||
|
text("Rooms you've been in — click to hop back. Best-effort: only works while someone's still there.")
|
||||||
|
.size(11)
|
||||||
|
.color(color_subtext),
|
||||||
|
vertical_space(10.0),
|
||||||
|
rows,
|
||||||
|
]
|
||||||
|
.spacing(6),
|
||||||
|
)
|
||||||
|
.style(c_style(color_mantle, color_surface, 12.0))
|
||||||
|
.padding(24)
|
||||||
|
.width(380)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn friends_panel(state: &AppState) -> Element<'_, AppMessage> {
|
fn friends_panel(state: &AppState) -> Element<'_, AppMessage> {
|
||||||
let pal = state.config.theme.palette();
|
let pal = state.config.theme.palette();
|
||||||
let color_crust = pal.crust;
|
let color_crust = pal.crust;
|
||||||
@@ -2255,14 +2299,28 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
|||||||
// fixed-width Connect card would otherwise squeeze it until its node-ID
|
// fixed-width Connect card would otherwise squeeze it until its node-ID
|
||||||
// field and remove button clip away. `responsive` measures the available
|
// field and remove button clip away. `responsive` measures the available
|
||||||
// width each layout pass and picks the orientation accordingly.
|
// width each layout pass and picks the orientation accordingly.
|
||||||
|
// The Recent-rooms card lives in the LEFT column under the Connect card
|
||||||
|
// (both are about getting into a room), so it has its own card and a
|
||||||
|
// growing history can't reflow the Create/Join controls. Friends stays on
|
||||||
|
// the right. Recents is omitted entirely (no stray gap) when empty.
|
||||||
let body = responsive(move |size| {
|
let body = responsive(move |size| {
|
||||||
|
let has_recents = !state.config.recents.is_empty();
|
||||||
let cards: Element<AppMessage> = if size.width < 900.0 {
|
let cards: Element<AppMessage> = if size.width < 900.0 {
|
||||||
column![connect_card(state), friends_panel(state)]
|
let mut col = column![connect_card(state)]
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
.align_x(iced::alignment::Horizontal::Center)
|
.align_x(iced::alignment::Horizontal::Center);
|
||||||
.into()
|
if has_recents {
|
||||||
|
col = col.push(recents_card(state));
|
||||||
|
}
|
||||||
|
col.push(friends_panel(state)).into()
|
||||||
} else {
|
} else {
|
||||||
row![connect_card(state), friends_panel(state)]
|
let mut left = column![connect_card(state)]
|
||||||
|
.spacing(20)
|
||||||
|
.align_x(iced::alignment::Horizontal::Center);
|
||||||
|
if has_recents {
|
||||||
|
left = left.push(recents_card(state));
|
||||||
|
}
|
||||||
|
row![left, friends_panel(state)]
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
.align_y(iced::alignment::Vertical::Top)
|
.align_y(iced::alignment::Vertical::Top)
|
||||||
.into()
|
.into()
|
||||||
|
|||||||
Reference in New Issue
Block a user