Connected Devices: fixed UI settings. fixed pairing protocol
This commit is contained in:
+2
-1
@@ -56,10 +56,11 @@ pub enum AppEvent {
|
||||
track_id: i64,
|
||||
liked: bool,
|
||||
},
|
||||
/// Liked federated item ids for the ♥ markers.
|
||||
/// Liked federated item ids and content ids for the ♥ markers.
|
||||
FedLikesLoaded(Result<Vec<String>, String>),
|
||||
FedLikeToggled {
|
||||
item_id: String,
|
||||
content_id: Option<String>,
|
||||
liked: bool,
|
||||
},
|
||||
/// A release fetched for queueing (a / shift-a on a release).
|
||||
|
||||
+28
-4
@@ -459,13 +459,12 @@ fn perform_effect(state: &mut AppState, runtime: &mut Runtime, effect: Effect) {
|
||||
for fed in fed_tracks {
|
||||
match library.toggle_fed_like(&fed) {
|
||||
Ok(liked) => {
|
||||
if let Err(err) =
|
||||
devices.record_fed_like(fed.content_id.as_deref(), liked)
|
||||
{
|
||||
if let Err(err) = devices.record_fed_like(&fed, liked) {
|
||||
tracing::warn!(%err, title = %fed.title, "recording synced federated like failed");
|
||||
}
|
||||
let _ = tx.send(AppEvent::FedLikeToggled {
|
||||
item_id: fed.item_id.clone(),
|
||||
content_id: fed.content_id.clone(),
|
||||
liked,
|
||||
});
|
||||
}
|
||||
@@ -524,6 +523,15 @@ fn perform_effect(state: &mut AppState, runtime: &mut Runtime, effect: Effect) {
|
||||
let _ = tx.send(AppEvent::FedTicket(result));
|
||||
});
|
||||
}
|
||||
Effect::DeviceShowInvite
|
||||
| Effect::DeviceConnectInvite(_)
|
||||
| Effect::DeviceSyncNow
|
||||
| Effect::DeviceSetName(_)
|
||||
| Effect::DeviceRevoke(_)
|
||||
if !state.connected_devices_enabled() =>
|
||||
{
|
||||
state.status_message = Some("enable federation before using connected devices".into());
|
||||
}
|
||||
Effect::DeviceShowInvite => {
|
||||
let fed = Arc::clone(&runtime.federation);
|
||||
let devices = Arc::clone(&runtime.devices);
|
||||
@@ -1393,6 +1401,8 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent
|
||||
device_id: request.device_id,
|
||||
name: request.name,
|
||||
client_version: request.client_version,
|
||||
requester_group_id: request.requester_group_id,
|
||||
requester_group_active_devices: request.requester_group_active_devices,
|
||||
});
|
||||
state.federation.devices = Some(runtime.devices.status());
|
||||
}
|
||||
@@ -1703,11 +1713,25 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent
|
||||
Ok(ids) => state.fed_likes = ids.into_iter().collect(),
|
||||
Err(message) => tracing::warn!(%message, "federated likes load failed"),
|
||||
},
|
||||
AppEvent::FedLikeToggled { item_id, liked } => {
|
||||
AppEvent::FedLikeToggled {
|
||||
item_id,
|
||||
content_id,
|
||||
liked,
|
||||
} => {
|
||||
if liked {
|
||||
state.fed_likes.insert(item_id);
|
||||
if let Some(content_id) =
|
||||
content_id.and_then(|id| music_dht::normalize_content_id(&id))
|
||||
{
|
||||
state.fed_likes.insert(content_id);
|
||||
}
|
||||
} else {
|
||||
state.fed_likes.remove(&item_id);
|
||||
if let Some(content_id) =
|
||||
content_id.and_then(|id| music_dht::normalize_content_id(&id))
|
||||
{
|
||||
state.fed_likes.remove(&content_id);
|
||||
}
|
||||
}
|
||||
// The virtual Likes playlist is stale now; refetch on next open.
|
||||
state.playlist_views.remove(&state::LIKES_PLAYLIST_ID);
|
||||
|
||||
+28
-3
@@ -66,6 +66,8 @@ pub fn handle_key(state: &mut AppState, runtime: &mut Runtime, key: KeyEvent) {
|
||||
device_id,
|
||||
name,
|
||||
client_version,
|
||||
requester_group_id,
|
||||
requester_group_active_devices,
|
||||
} => handle_device_pairing(
|
||||
state,
|
||||
runtime,
|
||||
@@ -73,6 +75,8 @@ pub fn handle_key(state: &mut AppState, runtime: &mut Runtime, key: KeyEvent) {
|
||||
device_id,
|
||||
name,
|
||||
client_version,
|
||||
requester_group_id,
|
||||
requester_group_active_devices,
|
||||
key,
|
||||
),
|
||||
Popup::ConfirmDeviceRevoke { device_id, name } => {
|
||||
@@ -165,11 +169,14 @@ fn handle_device_pairing(
|
||||
device_id: String,
|
||||
name: String,
|
||||
client_version: String,
|
||||
requester_group_id: Option<String>,
|
||||
requester_group_active_devices: usize,
|
||||
key: KeyEvent,
|
||||
) {
|
||||
let group_conflict = requester_group_id.is_some() && requester_group_active_devices > 1;
|
||||
match key.code {
|
||||
KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('q') => {
|
||||
if let Err(err) = runtime.devices.answer_pairing(&request_id, false) {
|
||||
if let Err(err) = runtime.devices.answer_pairing(&request_id, false, false) {
|
||||
state.status_message = Some(format!("pairing: {err:#}"));
|
||||
} else {
|
||||
state.status_message = Some("device pairing denied".to_string());
|
||||
@@ -177,10 +184,26 @@ fn handle_device_pairing(
|
||||
state.federation.devices = Some(runtime.devices.status());
|
||||
}
|
||||
KeyCode::Char('y') => {
|
||||
if let Err(err) = runtime.devices.answer_pairing(&request_id, true) {
|
||||
if let Err(err) = runtime
|
||||
.devices
|
||||
.answer_pairing(&request_id, true, group_conflict)
|
||||
{
|
||||
state.status_message = Some(format!("pairing: {err:#}"));
|
||||
} else {
|
||||
state.status_message = Some(format!("device \"{name}\" accepted"));
|
||||
state.status_message = Some(if group_conflict {
|
||||
format!("device \"{name}\" accepted; joining its sync group")
|
||||
} else {
|
||||
format!("device \"{name}\" accepted")
|
||||
});
|
||||
}
|
||||
state.federation.devices = Some(runtime.devices.status());
|
||||
}
|
||||
KeyCode::Char('c') if group_conflict => {
|
||||
if let Err(err) = runtime.devices.answer_pairing(&request_id, true, false) {
|
||||
state.status_message = Some(format!("pairing: {err:#}"));
|
||||
} else {
|
||||
state.status_message =
|
||||
Some(format!("device \"{name}\" accepted into this sync group"));
|
||||
}
|
||||
state.federation.devices = Some(runtime.devices.status());
|
||||
}
|
||||
@@ -190,6 +213,8 @@ fn handle_device_pairing(
|
||||
device_id,
|
||||
name,
|
||||
client_version,
|
||||
requester_group_id,
|
||||
requester_group_active_devices,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+31
-2
@@ -536,6 +536,8 @@ pub enum Popup {
|
||||
device_id: String,
|
||||
name: String,
|
||||
client_version: String,
|
||||
requester_group_id: Option<String>,
|
||||
requester_group_active_devices: usize,
|
||||
},
|
||||
/// Confirmation before revoking a trusted device.
|
||||
ConfirmDeviceRevoke { device_id: String, name: String },
|
||||
@@ -819,8 +821,8 @@ pub struct AppState {
|
||||
pub playlist_views: HashMap<i64, Loadable<PlaylistDetail>>,
|
||||
/// Liked track ids, for the ♥ markers everywhere tracks are shown.
|
||||
pub likes: std::collections::HashSet<i64>,
|
||||
/// Liked federated tracks (DHT item ids) — likes that reference peers'
|
||||
/// tracks without downloading them.
|
||||
/// Liked federated tracks (DHT item ids and content ids) — likes that
|
||||
/// reference peers' tracks without downloading them.
|
||||
pub fed_likes: std::collections::HashSet<String>,
|
||||
pub likes_loaded: bool,
|
||||
pub logs: LogsTab,
|
||||
@@ -848,3 +850,30 @@ pub struct AppState {
|
||||
/// every view that shows artwork.
|
||||
pub art: HashMap<String, ArtState>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn connected_devices_enabled(&self) -> bool {
|
||||
self.federation.settings.enabled && !self.federation.settings.network_id.trim().is_empty()
|
||||
}
|
||||
|
||||
pub fn fed_track_liked(&self, fed: &crate::federation::FedTrack) -> bool {
|
||||
self.fed_likes.contains(&fed.item_id)
|
||||
|| fed
|
||||
.content_id
|
||||
.as_deref()
|
||||
.and_then(music_dht::normalize_content_id)
|
||||
.is_some_and(|content_id| self.fed_likes.contains(&content_id))
|
||||
}
|
||||
|
||||
pub fn fed_card_track_liked(&self, track: &crate::federation::FedCardTrack) -> bool {
|
||||
track
|
||||
.content_id
|
||||
.as_deref()
|
||||
.and_then(music_dht::normalize_content_id)
|
||||
.is_some_and(|content_id| self.fed_likes.contains(&content_id))
|
||||
|| track
|
||||
.sources
|
||||
.iter()
|
||||
.any(|(_, item_id)| self.fed_likes.contains(item_id))
|
||||
}
|
||||
}
|
||||
|
||||
+32
-6
@@ -292,16 +292,14 @@ pub fn update(state: &mut AppState, action: Action) -> Option<Effect> {
|
||||
None
|
||||
} else {
|
||||
let should_like = track_ids.iter().any(|id| !state.likes.contains(id))
|
||||
|| fed_tracks
|
||||
.iter()
|
||||
.any(|fed| !state.fed_likes.contains(&fed.item_id));
|
||||
|| fed_tracks.iter().any(|fed| !state.fed_track_liked(fed));
|
||||
let toggles: Vec<i64> = track_ids
|
||||
.into_iter()
|
||||
.filter(|id| state.likes.contains(id) != should_like)
|
||||
.collect();
|
||||
let fed_toggles: Vec<crate::federation::FedTrack> = fed_tracks
|
||||
.into_iter()
|
||||
.filter(|fed| state.fed_likes.contains(&fed.item_id) != should_like)
|
||||
.filter(|fed| state.fed_track_liked(fed) != should_like)
|
||||
.collect();
|
||||
let total = toggles.len() + fed_toggles.len();
|
||||
state.status_message = Some(if should_like {
|
||||
@@ -2344,6 +2342,9 @@ fn federation_select(state: &mut AppState) -> Option<Effect> {
|
||||
None
|
||||
}
|
||||
SettingsRow::DeviceName => {
|
||||
if !require_connected_devices_enabled(state) {
|
||||
return None;
|
||||
}
|
||||
let name = state
|
||||
.federation
|
||||
.devices
|
||||
@@ -2356,16 +2357,32 @@ fn federation_select(state: &mut AppState) -> Option<Effect> {
|
||||
});
|
||||
None
|
||||
}
|
||||
SettingsRow::DeviceInvite => Some(Effect::DeviceShowInvite),
|
||||
SettingsRow::DeviceInvite => {
|
||||
if !require_connected_devices_enabled(state) {
|
||||
return None;
|
||||
}
|
||||
Some(Effect::DeviceShowInvite)
|
||||
}
|
||||
SettingsRow::DeviceConnect => {
|
||||
if !require_connected_devices_enabled(state) {
|
||||
return None;
|
||||
}
|
||||
state.popup = Some(Popup::FedInput {
|
||||
field: FedInputField::ConnectInvite,
|
||||
input: crate::app::input::LineEdit::default(),
|
||||
});
|
||||
None
|
||||
}
|
||||
SettingsRow::DeviceSyncNow => Some(Effect::DeviceSyncNow),
|
||||
SettingsRow::DeviceSyncNow => {
|
||||
if !require_connected_devices_enabled(state) {
|
||||
return None;
|
||||
}
|
||||
Some(Effect::DeviceSyncNow)
|
||||
}
|
||||
SettingsRow::Device(index) => {
|
||||
if !require_connected_devices_enabled(state) {
|
||||
return None;
|
||||
}
|
||||
let Some(device) = state
|
||||
.federation
|
||||
.devices
|
||||
@@ -2433,6 +2450,15 @@ fn federation_select(state: &mut AppState) -> Option<Effect> {
|
||||
}
|
||||
}
|
||||
|
||||
fn require_connected_devices_enabled(state: &mut AppState) -> bool {
|
||||
if state.connected_devices_enabled() {
|
||||
true
|
||||
} else {
|
||||
state.status_message = Some("enable federation before using connected devices".to_string());
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// A freshly created play context: drop the stale pre-shuffle snapshot and,
|
||||
/// if shuffle is on, shuffle everything after the chosen track right away.
|
||||
pub(super) fn on_new_queue(state: &mut AppState) {
|
||||
|
||||
Reference in New Issue
Block a user