From 4a61bfcde4ab374d28da2ed77c0bf9bb5449105f Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Wed, 3 Jan 2024 02:35:07 +0000 Subject: [PATCH] Split subviews from room view --- .gitignore | 1 + shalom/src/main.rs | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------------- shalom/src/pages/room.rs | 246 ++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ shalom/src/pages/room/lights.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ shalom/src/pages/room/listen.rs | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 429 insertions(+), 317 deletions(-) create mode 100644 shalom/src/pages/room/lights.rs create mode 100644 shalom/src/pages/room/listen.rs diff --git a/.gitignore b/.gitignore index fabfb87..0b3ec0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /config.toml +/.vscode diff --git a/shalom/src/main.rs b/shalom/src/main.rs index ef847f7..c826a2f 100644 --- a/shalom/src/main.rs +++ b/shalom/src/main.rs @@ -45,6 +45,120 @@ impl Shalom { self.oracle.as_ref().unwrap().clone(), )) } + + fn handle_room_event(&mut self, e: pages::room::Message) -> Command { + let ActivePage::Room(r) = &mut self.page else { + return Command::none(); + }; + + match r.update(e) { + Some(pages::room::Event::Lights(e)) => self.handle_light_event(e), + Some(pages::room::Event::Listen(e)) => self.handle_listen_event(e), + Some(pages::room::Event::Exit) => { + self.page = self.build_omni_route(); + Command::none() + } + None => Command::none(), + } + } + + fn handle_light_event(&mut self, event: pages::room::lights::Event) -> Command { + match event { + pages::room::lights::Event::SetLightState(id, state) => { + let oracle = self.oracle.as_ref().unwrap().clone(); + + Command::perform( + async move { oracle.set_light_state(id, state).await }, + Message::UpdateLightResult, + ) + } + pages::room::lights::Event::OpenLightContextMenu(id) => { + if let Some(light) = self.oracle.as_ref().and_then(|o| o.fetch_light(id)) { + self.context_menu = Some(ActiveContextMenu::LightControl( + context_menus::light_control::LightControl::new(id, light), + )); + } + + Command::none() + } + } + } + + fn handle_listen_event(&self, event: pages::room::listen::Event) -> Command { + match event { + pages::room::listen::Event::SetSpeakerVolume(id, new) => { + let oracle = self.oracle.as_ref().unwrap().clone(); + + Command::perform( + async move { oracle.speaker(id).set_volume(new).await }, + Message::UpdateLightResult, + ) + } + pages::room::listen::Event::SetSpeakerPosition(id, new) => { + let oracle = self.oracle.as_ref().unwrap().clone(); + + Command::perform( + async move { oracle.speaker(id).seek(new).await }, + Message::UpdateLightResult, + ) + } + pages::room::listen::Event::SetSpeakerPlaying(id, new) => { + let oracle = self.oracle.as_ref().unwrap().clone(); + + Command::perform( + async move { + let speaker = oracle.speaker(id); + if new { + speaker.play().await; + } else { + speaker.pause().await; + } + }, + Message::UpdateLightResult, + ) + } + pages::room::listen::Event::SetSpeakerMuted(id, new) => { + let oracle = self.oracle.as_ref().unwrap().clone(); + + Command::perform( + async move { oracle.speaker(id).set_mute(new).await }, + Message::UpdateLightResult, + ) + } + pages::room::listen::Event::SetSpeakerRepeat(id, new) => { + let oracle = self.oracle.as_ref().unwrap().clone(); + + Command::perform( + async move { oracle.speaker(id).set_repeat(new).await }, + Message::UpdateLightResult, + ) + } + pages::room::listen::Event::SpeakerNextTrack(id) => { + let oracle = self.oracle.as_ref().unwrap().clone(); + + Command::perform( + async move { oracle.speaker(id).next().await }, + Message::UpdateLightResult, + ) + } + pages::room::listen::Event::SpeakerPreviousTrack(id) => { + let oracle = self.oracle.as_ref().unwrap().clone(); + + Command::perform( + async move { oracle.speaker(id).previous().await }, + Message::UpdateLightResult, + ) + } + pages::room::listen::Event::SetSpeakerShuffle(id, new) => { + let oracle = self.oracle.as_ref().unwrap().clone(); + + Command::perform( + async move { oracle.speaker(id).set_shuffle(new).await }, + Message::UpdateLightResult, + ) + } + } + } } impl Application for Shalom { @@ -109,101 +223,7 @@ impl Application for Shalom { } None => Command::none(), }, - (Message::RoomEvent(e), ActivePage::Room(r), _) => match r.update(e) { - Some(pages::room::Event::OpenLightContextMenu(id)) => { - if let Some(light) = self.oracle.as_ref().and_then(|o| o.fetch_light(id)) { - self.context_menu = Some(ActiveContextMenu::LightControl( - context_menus::light_control::LightControl::new(id, light), - )); - } - - Command::none() - } - Some(pages::room::Event::SetLightState(id, state)) => { - let oracle = self.oracle.as_ref().unwrap().clone(); - - Command::perform( - async move { oracle.set_light_state(id, state).await }, - Message::UpdateLightResult, - ) - } - Some(pages::room::Event::SetSpeakerVolume(id, new)) => { - let oracle = self.oracle.as_ref().unwrap().clone(); - - Command::perform( - async move { oracle.speaker(id).set_volume(new).await }, - Message::UpdateLightResult, - ) - } - Some(pages::room::Event::SetSpeakerPosition(id, new)) => { - let oracle = self.oracle.as_ref().unwrap().clone(); - - Command::perform( - async move { oracle.speaker(id).seek(new).await }, - Message::UpdateLightResult, - ) - } - Some(pages::room::Event::SetSpeakerPlaying(id, new)) => { - let oracle = self.oracle.as_ref().unwrap().clone(); - - Command::perform( - async move { - let speaker = oracle.speaker(id); - if new { - speaker.play().await; - } else { - speaker.pause().await; - } - }, - Message::UpdateLightResult, - ) - } - Some(pages::room::Event::SetSpeakerMuted(id, new)) => { - let oracle = self.oracle.as_ref().unwrap().clone(); - - Command::perform( - async move { oracle.speaker(id).set_mute(new).await }, - Message::UpdateLightResult, - ) - } - Some(pages::room::Event::SetSpeakerRepeat(id, new)) => { - let oracle = self.oracle.as_ref().unwrap().clone(); - - Command::perform( - async move { oracle.speaker(id).set_repeat(new).await }, - Message::UpdateLightResult, - ) - } - Some(pages::room::Event::SpeakerNextTrack(id)) => { - let oracle = self.oracle.as_ref().unwrap().clone(); - - Command::perform( - async move { oracle.speaker(id).next().await }, - Message::UpdateLightResult, - ) - } - Some(pages::room::Event::SpeakerPreviousTrack(id)) => { - let oracle = self.oracle.as_ref().unwrap().clone(); - - Command::perform( - async move { oracle.speaker(id).previous().await }, - Message::UpdateLightResult, - ) - } - Some(pages::room::Event::SetSpeakerShuffle(id, new)) => { - let oracle = self.oracle.as_ref().unwrap().clone(); - - Command::perform( - async move { oracle.speaker(id).set_shuffle(new).await }, - Message::UpdateLightResult, - ) - } - Some(pages::room::Event::Exit) => { - self.page = self.build_omni_route(); - Command::none() - } - None => Command::none(), - }, + (Message::RoomEvent(e), _, _) => self.handle_room_event(e), (Message::LightControlMenu(e), _, Some(ActiveContextMenu::LightControl(menu))) => { match menu.update(e) { Some(context_menus::light_control::Event::UpdateLightColour { diff --git a/shalom/src/pages/room.rs b/shalom/src/pages/room.rs index ce26d64..233dd18 100644 --- a/shalom/src/pages/room.rs +++ b/shalom/src/pages/room.rs @@ -1,23 +1,19 @@ -use std::{collections::BTreeMap, sync::Arc, time::Duration}; +pub mod lights; +pub mod listen; + +use std::sync::Arc; use iced::{ advanced::graphics::core::Element, font::{Stretch, Weight}, - futures::StreamExt, - subscription, theme, - widget::{container, image::Handle, row, text, Column, Row}, + theme, + widget::{row, text, Column}, Color, Font, Length, Renderer, Subscription, }; -use url::Url; use crate::{ - hass_client::MediaPlayerRepeat, - oracle::{Light, MediaPlayerSpeaker, MediaPlayerSpeakerState, Oracle}, - subscriptions::download_image, - theme::Icon, - widgets, + oracle::Oracle, widgets::{ - colour_picker::colour_from_hsb, image_background::image_background, room_navigation::{Page, RoomNavigation}, }, @@ -26,28 +22,21 @@ use crate::{ #[derive(Debug)] pub struct Room { id: &'static str, - oracle: Arc, room: crate::oracle::Room, - speaker: Option<(&'static str, MediaPlayerSpeaker)>, - now_playing_image: Option, - lights: BTreeMap<&'static str, Light>, + lights: lights::Lights, + listen: listen::Listen, current_page: Page, } impl Room { pub fn new(id: &'static str, oracle: Arc) -> Self { let room = oracle.room(id).clone(); - let speaker = room.speaker(&oracle); - - let lights = room.lights(&oracle); Self { id, - oracle, + listen: listen::Listen::new(oracle.clone(), &room), + lights: lights::Lights::new(oracle, &room), room, - speaker, - now_playing_image: None, - lights, current_page: Page::Listen, } } @@ -58,91 +47,8 @@ impl Room { pub fn update(&mut self, event: Message) -> Option { match event { - Message::SetLightState(id, state) => { - // give instant feedback before we get the event back from hass - if let Some(light) = self.lights.get_mut(id) { - light.on = Some(state); - } - - Some(Event::SetLightState(id, state)) - } - Message::OpenLightOptions(id) => Some(Event::OpenLightContextMenu(id)), - Message::NowPlayingImageLoaded(url, handle) => { - if self - .speaker - .as_ref() - .and_then(|(_, v)| v.entity_picture.as_ref()) - == Some(&url) - { - self.now_playing_image = Some(handle); - } - - None - } - Message::UpdateSpeaker => { - let new = self.room.speaker(&self.oracle); - - if self - .speaker - .as_ref() - .and_then(|(_, v)| v.entity_picture.as_ref()) - != new - .as_ref() - .as_ref() - .and_then(|(_, v)| v.entity_picture.as_ref()) - { - self.now_playing_image = None; - } - - self.speaker = new; - - None - } - Message::UpdateLight(entity_id) => { - if let Some(light) = self.oracle.fetch_light(entity_id) { - self.lights.insert(entity_id, light); - } - - None - } - Message::OnSpeakerVolumeChange(new) => { - let (id, speaker) = self.speaker.as_mut()?; - speaker.volume = new; - Some(Event::SetSpeakerVolume(id, new)) - } - Message::OnSpeakerPositionChange(new) => { - let (id, speaker) = self.speaker.as_mut()?; - speaker.actual_media_position = Some(new); - Some(Event::SetSpeakerPosition(id, new)) - } - Message::OnSpeakerStateChange(new) => { - let (id, speaker) = self.speaker.as_mut()?; - speaker.state = if new { - MediaPlayerSpeakerState::Playing - } else { - MediaPlayerSpeakerState::Paused - }; - Some(Event::SetSpeakerPlaying(id, new)) - } - Message::OnSpeakerMuteChange(new) => { - let (id, speaker) = self.speaker.as_mut()?; - speaker.muted = new; - Some(Event::SetSpeakerMuted(id, new)) - } - Message::OnSpeakerRepeatChange(new) => { - let (id, speaker) = self.speaker.as_mut()?; - speaker.repeat = new; - Some(Event::SetSpeakerRepeat(id, new)) - } - Message::OnSpeakerNextTrack => Some(Event::SpeakerNextTrack(self.speaker.as_ref()?.0)), - Message::OnSpeakerPreviousTrack => { - Some(Event::SpeakerPreviousTrack(self.speaker.as_ref()?.0)) - } - Message::OnSpeakerShuffleChange(new) => { - let (id, speaker) = self.speaker.as_mut()?; - speaker.shuffle = new; - Some(Event::SetSpeakerShuffle(id, new)) - } + Message::Lights(v) => self.lights.update(v).map(Event::Lights), + Message::Listen(v) => self.listen.update(v).map(Event::Listen), Message::ChangePage(page) => { self.current_page = page; None @@ -161,63 +67,13 @@ impl Room { }) .style(theme::Text::Color(Color::WHITE)); - let light = |id, light: &Light| { - let mut toggle_card = widgets::toggle_card::toggle_card( - &light.friendly_name, - light.on.unwrap_or_default(), - light.on.is_none(), - ) - .icon(Icon::Bulb) - .active_icon_colour( - light - .hs_color - .zip(light.brightness) - .map(|((h, s), b)| colour_from_hsb(h, s, b / 255.)), - ); - - if let Some(state) = light.on { - toggle_card = toggle_card - .on_press(Message::SetLightState(id, !state)) - .on_long_press(Message::OpenLightOptions(id)); - } - - toggle_card - }; - let mut col = Column::new().spacing(20).padding(40).push(header); - match self.current_page { - Page::Climate => {} - Page::Listen => { - if let Some((_, speaker)) = self.speaker.clone() { - col = col.push(container( - widgets::media_player::media_player( - speaker, - self.now_playing_image.clone(), - ) - .on_volume_change(Message::OnSpeakerVolumeChange) - .on_mute_change(Message::OnSpeakerMuteChange) - .on_repeat_change(Message::OnSpeakerRepeatChange) - .on_state_change(Message::OnSpeakerStateChange) - .on_position_change(Message::OnSpeakerPositionChange) - .on_next_track(Message::OnSpeakerNextTrack) - .on_previous_track(Message::OnSpeakerPreviousTrack) - .on_shuffle_change(Message::OnSpeakerShuffleChange), - )); - } - } - Page::Lights => { - let lights = Row::with_children( - self.lights - .iter() - .map(|(id, item)| light(*id, item)) - .map(Element::from) - .collect::>(), - ) - .spacing(10); - col = col.push(lights); - } - } + col = col.push(match self.current_page { + Page::Climate => Element::from(row![]), + Page::Listen => self.listen.view().map(Message::Listen), + Page::Lights => self.lights.view().map(Message::Lights), + }); row![ RoomNavigation::new(self.current_page) @@ -234,77 +90,23 @@ impl Room { } pub fn subscription(&self) -> Subscription { - let image_subscription = if let (Some(uri), None) = ( - self.speaker - .as_ref() - .and_then(|(_, v)| v.entity_picture.as_ref()), - &self.now_playing_image, - ) { - download_image("now-playing", uri.clone(), |_, url, handle| { - Message::NowPlayingImageLoaded(url, handle) - }) - } else { - Subscription::none() - }; - - let speaker_subscription = if let Some(speaker_id) = self.speaker.as_ref().map(|(k, _)| *k) - { - subscription::run_with_id( - speaker_id, - self.oracle - .subscribe_id(speaker_id) - .map(|()| Message::UpdateSpeaker), - ) - } else { - Subscription::none() - }; - - let light_subscriptions = Subscription::batch(self.lights.keys().copied().map(|key| { - subscription::run_with_id( - key, - self.oracle - .subscribe_id(key) - .map(|()| Message::UpdateLight(key)), - ) - })); - Subscription::batch([ - image_subscription, - speaker_subscription, - light_subscriptions, + self.listen.subscription().map(Message::Listen), + self.lights.subscription().map(Message::Lights), ]) } } pub enum Event { - OpenLightContextMenu(&'static str), - SetLightState(&'static str, bool), - SetSpeakerVolume(&'static str, f32), - SetSpeakerPosition(&'static str, Duration), - SetSpeakerPlaying(&'static str, bool), - SetSpeakerMuted(&'static str, bool), - SetSpeakerShuffle(&'static str, bool), - SetSpeakerRepeat(&'static str, MediaPlayerRepeat), - SpeakerNextTrack(&'static str), - SpeakerPreviousTrack(&'static str), + Lights(lights::Event), + Listen(listen::Event), Exit, } #[derive(Clone, Debug)] pub enum Message { - NowPlayingImageLoaded(Url, Handle), - SetLightState(&'static str, bool), - OpenLightOptions(&'static str), - UpdateSpeaker, - UpdateLight(&'static str), - OnSpeakerVolumeChange(f32), - OnSpeakerPositionChange(Duration), - OnSpeakerStateChange(bool), - OnSpeakerMuteChange(bool), - OnSpeakerShuffleChange(bool), - OnSpeakerRepeatChange(MediaPlayerRepeat), - OnSpeakerNextTrack, - OnSpeakerPreviousTrack, + Lights(lights::Message), + Listen(listen::Message), ChangePage(Page), Exit, } diff --git a/shalom/src/pages/room/lights.rs b/shalom/src/pages/room/lights.rs new file mode 100644 index 0000000..4eebeb4 --- /dev/null +++ b/shalom/src/pages/room/lights.rs @@ -0,0 +1,103 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use iced::{futures::StreamExt, subscription, widget::Row, Element, Renderer, Subscription}; + +use crate::{ + oracle::{Light, Oracle, Room}, + theme::Icon, + widgets::{self, colour_picker::colour_from_hsb}, +}; + +#[derive(Debug)] +pub struct Lights { + lights: BTreeMap<&'static str, Light>, + oracle: Arc, +} + +impl Lights { + pub fn new(oracle: Arc, room: &Room) -> Self { + let lights = room.lights(&oracle); + + Self { lights, oracle } + } + + pub fn update(&mut self, event: Message) -> Option { + match event { + Message::SetLightState(id, state) => { + // give instant feedback before we get the event back from hass + if let Some(light) = self.lights.get_mut(id) { + light.on = Some(state); + } + + Some(Event::SetLightState(id, state)) + } + Message::OpenLightOptions(id) => Some(Event::OpenLightContextMenu(id)), + Message::UpdateLight(entity_id) => { + if let Some(light) = self.oracle.fetch_light(entity_id) { + self.lights.insert(entity_id, light); + } + + None + } + } + } + + pub fn view(&self) -> Element<'_, Message, Renderer> { + let light = |id, light: &Light| { + let mut toggle_card = widgets::toggle_card::toggle_card( + &light.friendly_name, + light.on.unwrap_or_default(), + light.on.is_none(), + ) + .icon(Icon::Bulb) + .active_icon_colour( + light + .hs_color + .zip(light.brightness) + .map(|((h, s), b)| colour_from_hsb(h, s, b / 255.)), + ); + + if let Some(state) = light.on { + toggle_card = toggle_card + .on_press(Message::SetLightState(id, !state)) + .on_long_press(Message::OpenLightOptions(id)); + } + + toggle_card + }; + + Row::with_children( + self.lights + .iter() + .map(|(id, item)| light(*id, item)) + .map(Element::from) + .collect::>(), + ) + .spacing(10) + .into() + } + + pub fn subscription(&self) -> Subscription { + Subscription::batch(self.lights.keys().copied().map(|key| { + subscription::run_with_id( + key, + self.oracle + .subscribe_id(key) + .map(|()| Message::UpdateLight(key)), + ) + })) + } +} + +#[derive(Copy, Clone)] +pub enum Event { + OpenLightContextMenu(&'static str), + SetLightState(&'static str, bool), +} + +#[derive(Clone, Debug, Copy)] +pub enum Message { + SetLightState(&'static str, bool), + UpdateLight(&'static str), + OpenLightOptions(&'static str), +} diff --git a/shalom/src/pages/room/listen.rs b/shalom/src/pages/room/listen.rs new file mode 100644 index 0000000..981cca9 --- /dev/null +++ b/shalom/src/pages/room/listen.rs @@ -0,0 +1,186 @@ +use std::{sync::Arc, time::Duration}; + +use iced::{ + futures::StreamExt, + subscription, + widget::{container, image::Handle, Column}, + Element, Renderer, Subscription, +}; +use url::Url; + +use crate::{ + hass_client::MediaPlayerRepeat, + oracle::{MediaPlayerSpeaker, MediaPlayerSpeakerState, Oracle, Room}, + subscriptions::download_image, + widgets, +}; + +#[derive(Debug)] +pub struct Listen { + room: Room, + oracle: Arc, + speaker: Option<(&'static str, MediaPlayerSpeaker)>, + now_playing_image: Option, +} + +impl Listen { + pub fn new(oracle: Arc, room: &Room) -> Self { + let speaker = room.speaker(&oracle); + + Self { + room: room.clone(), + speaker, + oracle, + now_playing_image: None, + } + } + + pub fn update(&mut self, event: Message) -> Option { + match event { + Message::NowPlayingImageLoaded(url, handle) => { + if self + .speaker + .as_ref() + .and_then(|(_, v)| v.entity_picture.as_ref()) + == Some(&url) + { + self.now_playing_image = Some(handle); + } + + None + } + Message::UpdateSpeaker => { + let new = self.room.speaker(&self.oracle); + + if self + .speaker + .as_ref() + .and_then(|(_, v)| v.entity_picture.as_ref()) + != new + .as_ref() + .as_ref() + .and_then(|(_, v)| v.entity_picture.as_ref()) + { + self.now_playing_image = None; + } + + self.speaker = new; + + None + } + Message::OnSpeakerVolumeChange(new) => { + let (id, speaker) = self.speaker.as_mut()?; + speaker.volume = new; + Some(Event::SetSpeakerVolume(id, new)) + } + Message::OnSpeakerPositionChange(new) => { + let (id, speaker) = self.speaker.as_mut()?; + speaker.actual_media_position = Some(new); + Some(Event::SetSpeakerPosition(id, new)) + } + Message::OnSpeakerStateChange(new) => { + let (id, speaker) = self.speaker.as_mut()?; + speaker.state = if new { + MediaPlayerSpeakerState::Playing + } else { + MediaPlayerSpeakerState::Paused + }; + Some(Event::SetSpeakerPlaying(id, new)) + } + Message::OnSpeakerMuteChange(new) => { + let (id, speaker) = self.speaker.as_mut()?; + speaker.muted = new; + Some(Event::SetSpeakerMuted(id, new)) + } + Message::OnSpeakerRepeatChange(new) => { + let (id, speaker) = self.speaker.as_mut()?; + speaker.repeat = new; + Some(Event::SetSpeakerRepeat(id, new)) + } + Message::OnSpeakerNextTrack => Some(Event::SpeakerNextTrack(self.speaker.as_ref()?.0)), + Message::OnSpeakerPreviousTrack => { + Some(Event::SpeakerPreviousTrack(self.speaker.as_ref()?.0)) + } + Message::OnSpeakerShuffleChange(new) => { + let (id, speaker) = self.speaker.as_mut()?; + speaker.shuffle = new; + Some(Event::SetSpeakerShuffle(id, new)) + } + } + } + + pub fn view(&self) -> Element<'_, Message, Renderer> { + let mut col = Column::new(); + + if let Some((_, speaker)) = self.speaker.clone() { + col = col.push(container( + widgets::media_player::media_player(speaker, self.now_playing_image.clone()) + .on_volume_change(Message::OnSpeakerVolumeChange) + .on_mute_change(Message::OnSpeakerMuteChange) + .on_repeat_change(Message::OnSpeakerRepeatChange) + .on_state_change(Message::OnSpeakerStateChange) + .on_position_change(Message::OnSpeakerPositionChange) + .on_next_track(Message::OnSpeakerNextTrack) + .on_previous_track(Message::OnSpeakerPreviousTrack) + .on_shuffle_change(Message::OnSpeakerShuffleChange), + )); + } + + col.into() + } + + pub fn subscription(&self) -> Subscription { + let image_subscription = if let (Some(uri), None) = ( + self.speaker + .as_ref() + .and_then(|(_, v)| v.entity_picture.as_ref()), + &self.now_playing_image, + ) { + download_image("now-playing", uri.clone(), |_, url, handle| { + Message::NowPlayingImageLoaded(url, handle) + }) + } else { + Subscription::none() + }; + + let speaker_subscription = if let Some(speaker_id) = self.speaker.as_ref().map(|(k, _)| *k) + { + subscription::run_with_id( + speaker_id, + self.oracle + .subscribe_id(speaker_id) + .map(|()| Message::UpdateSpeaker), + ) + } else { + Subscription::none() + }; + + Subscription::batch([image_subscription, speaker_subscription]) + } +} + +#[derive(Copy, Clone)] +pub enum Event { + SetSpeakerVolume(&'static str, f32), + SetSpeakerPosition(&'static str, Duration), + SetSpeakerPlaying(&'static str, bool), + SetSpeakerMuted(&'static str, bool), + SetSpeakerShuffle(&'static str, bool), + SetSpeakerRepeat(&'static str, MediaPlayerRepeat), + SpeakerNextTrack(&'static str), + SpeakerPreviousTrack(&'static str), +} + +#[derive(Clone, Debug)] +pub enum Message { + NowPlayingImageLoaded(Url, Handle), + UpdateSpeaker, + OnSpeakerVolumeChange(f32), + OnSpeakerPositionChange(Duration), + OnSpeakerStateChange(bool), + OnSpeakerMuteChange(bool), + OnSpeakerShuffleChange(bool), + OnSpeakerRepeatChange(MediaPlayerRepeat), + OnSpeakerNextTrack, + OnSpeakerPreviousTrack, +} -- libgit2 1.7.2