diff --git a/src/sonos.rs b/src/sonos.rs index d48cb25..b039021 100644 --- a/src/sonos.rs +++ b/src/sonos.rs @@ -1182,22 +1182,14 @@ fn group_states(topology: &Topology) -> Vec { .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(), @@ -1211,6 +1203,16 @@ fn group_states(topology: &Topology) -> Vec { 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 { let mut players: Vec<_> = topology @@ -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( @@ -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()], ), @@ -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"); diff --git a/src/view.rs b/src/view.rs index 33d1f9e..621b68e 100644 --- a/src/view.rs +++ b/src/view.rs @@ -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()], ), @@ -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()], ), @@ -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] @@ -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:#?}" ); } @@ -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}"); @@ -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 @@ -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:?}"); @@ -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] diff --git a/src/view/system_info.rs b/src/view/system_info.rs index fe97941..cb42738 100644 --- a/src/view/system_info.rs +++ b/src/view/system_info.rs @@ -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",