Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions src/sonos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1182,22 +1182,14 @@ fn group_states(topology: &Topology) -> Vec<GroupState> {
.groups
.iter()
.map(|group| {
// Make sure the coordinator comes first, so its name gets displayed first.
let coordinator = topology.players.get(&group.coordinator_id);
let others = group
.player_ids
.iter()
.filter(|id| **id != group.coordinator_id)
.filter_map(|id| topology.players.get(id));
let names: Vec<_> = coordinator
.into_iter()
.chain(others)
.map(|p| p.name.as_str())
.collect();
let name = if names.is_empty() {
group.name.clone()
} else {
names.join(" + ")
.count();
let name = match topology.players.get(&group.coordinator_id) {
Some(coordinator) => group_name(&coordinator.name, others),
None => group.name.clone(),
};
GroupState::new(
group.id.clone(),
Expand All @@ -1211,6 +1203,16 @@ fn group_states(topology: &Topology) -> Vec<GroupState> {
groups
}

/// The name of a group, as the Sonos app shows it: that of its coordinator, followed by how many
/// other players are in it, e.g. `Kitchen + 2`.
fn group_name(coordinator: &str, others: usize) -> String {
if others == 0 {
coordinator.to_owned()
} else {
format!("{coordinator} + {others}")
}
}

/// The players of the household, sorted by name.
fn player_infos(topology: &Topology) -> Vec<PlayerInfo> {
let mut players: Vec<_> = topology
Expand Down Expand Up @@ -1371,6 +1373,13 @@ mod tests {
assert!(command.merge(player_volume("o", -1)).is_err());
}

#[test]
fn groups_are_named_after_their_coordinator_and_how_many_others_they_have() {
assert_eq!(group_name("Kitchen", 0), "Kitchen");
assert_eq!(group_name("Kitchen", 1), "Kitchen + 1");
assert_eq!(group_name("Living Room", 3), "Living Room + 3");
}

#[test]
fn group_states_are_kept_across_topology_changes() {
let mut kitchen = GroupState::new(
Expand All @@ -1391,7 +1400,7 @@ mod tests {
let mut groups = [
GroupState::new(
"kitchen".into(),
"Kitchen + Office".to_owned(),
"Kitchen + 1".to_owned(),
"k".into(),
vec!["k".into(), "o".into()],
),
Expand All @@ -1404,7 +1413,7 @@ mod tests {
];
keep_group_states(&mut groups, &old_groups);

assert_eq!(groups[0].name, "Kitchen + Office");
assert_eq!(groups[0].name, "Kitchen + 1");
assert_eq!(groups[0].player_ids, ["k".into(), "o".into()]);
assert_eq!(groups[0].volume, Some(20));
assert_eq!(groups[1].name, "Bedroom");
Expand Down
23 changes: 10 additions & 13 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ mod tests {
groups: vec![
GroupState::new(
"bedroom".into(),
"Bedroom + Office".to_owned(),
"Bedroom + 1".to_owned(),
"b".into(),
vec!["b".into(), "o".into()],
),
Expand Down Expand Up @@ -681,21 +681,18 @@ mod tests {
// it joined keeps its ID.
state.groups = vec![GroupState::new(
"bedroom".into(),
"Bedroom + Office + Kitchen".to_owned(),
"Bedroom + 2".to_owned(),
"b".into(),
vec!["b".into(), "o".into(), "k".into()],
)];
ui.sync(&state);
assert_eq!(
ui.current_group(&state).unwrap().1.name,
"Bedroom + Office + Kitchen"
);
assert_eq!(ui.current_group(&state).unwrap().1.name, "Bedroom + 2");

// The kitchen left again, getting a group with a new ID: the group shown stays.
state.groups = vec![
GroupState::new(
"bedroom".into(),
"Bedroom + Office".to_owned(),
"Bedroom + 1".to_owned(),
"b".into(),
vec!["b".into(), "o".into()],
),
Expand All @@ -707,7 +704,7 @@ mod tests {
),
];
ui.sync(&state);
assert_eq!(ui.current_group(&state).unwrap().1.name, "Bedroom + Office");
assert_eq!(ui.current_group(&state).unwrap().1.name, "Bedroom + 1");
}

#[test]
Expand Down Expand Up @@ -1133,7 +1130,7 @@ mod tests {
assert!(!rows[0].contains("Bedroom"), "{}", rows[0]);
assert!(
rows.iter()
.any(|row| row.trim() == "Playing on Bedroom + Office"),
.any(|row| row.trim() == "Playing on Bedroom + 1"),
"{rows:#?}"
);
}
Expand Down Expand Up @@ -1172,7 +1169,7 @@ mod tests {
};
assert!(info.starts_with(" Nocturne · TesseracT "), "{info}");
assert!(
info.ends_with(" Spotify · Album · Altered State Playing on Bedroom + Office "),
info.ends_with(" Spotify · Album · Altered State Playing on Bedroom + 1 "),
"{info}"
);
assert!(progress.contains("-5:50"), "{progress}");
Expand All @@ -1186,7 +1183,7 @@ mod tests {
let [.., info, _, _, _] = rows.as_slice() else {
panic!("{rows:?}");
};
assert!(info.ends_with(" Playing on Bedroom + Office "), "{info}");
assert!(info.ends_with(" Playing on Bedroom + 1 "), "{info}");
assert!(!info.contains("Spotify"), "{info}");

// Without room for the name of the group, nor for the play modes beside the volume
Expand All @@ -1199,7 +1196,7 @@ mod tests {
assert!(controls.ends_with(" 13 "), "{controls}");

// With room for the source, but not for a longer name of the group
state.groups[0].name = "Bedroom + Office + Kitchen + Living Room".to_owned();
state.groups[0].name = "Upstairs Master Bedroom by the Window + 3".to_owned();
let rows = screen_rows(&state, &mut ui, 70);
let [.., info, _, _, _] = rows.as_slice() else {
panic!("{rows:?}");
Expand All @@ -1208,7 +1205,7 @@ mod tests {
info.ends_with(" Spotify · Album · Altered State "),
"{info}"
);
assert!(!info.contains("Kitchen"), "{info}");
assert!(!info.contains("Upstairs"), "{info}");
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src/view/system_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ mod tests {
let screen = screen(&state, &mut ui, 80, 60).join("\n");
for shown in [
"Household Sonos_1",
"Group Bedroom + Office · coordinator",
"Group Bedroom + 1 · coordinator",
"Address 10.0.0.2",
"Battery 15% (measured 12%) · not charging · 31.5 °C",
"Wi-Fi Home · excellent",
Expand Down
Loading