🏡 index : ~doyle/shalom.git

author Jordan Doyle <jordan@doyle.la> 2024-01-11 0:57:18.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2024-01-11 0:57:18.0 +00:00:00
commit
697689c3ab4289810e1dc84c89245a70bf008c00 [patch]
tree
0e062f7aab2bf89a5b39a5900cbaaeeb5980d865
parent
7ffc733434d2dc90b5287b68991a32d9f4480054
download
697689c3ab4289810e1dc84c89245a70bf008c00.tar.gz

Add listen search interface



Diff

 assets/images/album_art_test.jpg       |   3 +-
 shalom/src/main.rs                     |   4 +-
 shalom/src/pages/room.rs               |  16 ++---
 shalom/src/pages/room/listen.rs        |  35 ++++++++----
 shalom/src/pages/room/listen/search.rs | 102 ++++++++++++++++++++++++++++++++++-
 shalom/src/theme.rs                    |   2 +-
 shalom/src/widgets/mouse_area.rs       |   4 +-
 7 files changed, 143 insertions(+), 23 deletions(-)

diff --git a/assets/images/album_art_test.jpg b/assets/images/album_art_test.jpg
new file mode 100644
index 0000000..0aac16c
--- /dev/null
+++ b/assets/images/album_art_test.jpg
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4cae52d3c493233ab1681825b15059315572c035937c42937c829e6b359188b8
size 15475
diff --git a/shalom/src/main.rs b/shalom/src/main.rs
index 973c15b..46b7a4e 100644
--- a/shalom/src/main.rs
+++ b/shalom/src/main.rs
@@ -25,6 +25,7 @@ pub struct Shalom {
    context_menu: Option<ActiveContextMenu>,
    oracle: Option<Arc<Oracle>>,
    home_room: Option<&'static str>,
    theme: Theme,
}

impl Shalom {
@@ -175,6 +176,7 @@ impl Application for Shalom {
            context_menu: None,
            oracle: None,
            home_room: Some("living_room"),
            theme: Theme::default(),
        };

        // this is only best-effort to try and prevent blocking when loading
@@ -251,7 +253,7 @@ impl Application for Shalom {
    fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
        let page_content = match &self.page {
            ActivePage::Loading => Element::from(column!["Loading...",].spacing(20)),
            ActivePage::Room(room) => room.view().map(Message::RoomEvent),
            ActivePage::Room(room) => room.view(&self.theme).map(Message::RoomEvent),
            ActivePage::Omni(omni) => omni.view().map(Message::OmniEvent),
        };

diff --git a/shalom/src/pages/room.rs b/shalom/src/pages/room.rs
index 406fc0c..bb8fafc 100644
--- a/shalom/src/pages/room.rs
+++ b/shalom/src/pages/room.rs
@@ -7,8 +7,8 @@ use iced::{
    advanced::graphics::core::Element,
    font::{Stretch, Weight},
    theme,
    widget::{container, lazy, row, text, Column},
    Color, Font, Length, Renderer, Subscription,
    widget::{container, row, text, Column},
    Color, Font, Length, Renderer, Subscription, Theme,
};

use crate::{
@@ -58,7 +58,7 @@ impl Room {
        }
    }

    pub fn view(&self) -> Element<'_, Message, Renderer> {
    pub fn view(&self, style: &Theme) -> Element<'_, Message, Renderer> {
        let header = text(self.room.name.as_ref())
            .size(60)
            .font(Font {
@@ -69,11 +69,9 @@ impl Room {
            .style(theme::Text::Color(Color::WHITE));

        let header = if let Page::Listen = self.current_page {
            Element::from(lazy(self.room.name.as_ref(), move |_| {
                self.listen
                    .header_magic(header.clone())
                    .map(Message::Listen)
            }))
            self.listen
                .header_magic(header.clone())
                .map(Message::Listen)
        } else {
            Element::from(header)
        };
@@ -84,7 +82,7 @@ impl Room {

        col = col.push(match self.current_page {
            Page::Climate => Element::from(row![]),
            Page::Listen => self.listen.view().map(Message::Listen),
            Page::Listen => self.listen.view(style).map(Message::Listen),
            Page::Lights => container(self.lights.view().map(Message::Lights))
                .padding([0, 40, 0, 40])
                .into(),
diff --git a/shalom/src/pages/room/listen.rs b/shalom/src/pages/room/listen.rs
index be060e5..71e3dc7 100644
--- a/shalom/src/pages/room/listen.rs
+++ b/shalom/src/pages/room/listen.rs
@@ -1,10 +1,12 @@
mod search;

use std::{convert::identity, sync::Arc, time::Duration};

use iced::{
    futures::StreamExt,
    subscription,
    widget::{container, image::Handle, Column, Text},
    Element, Renderer, Subscription,
    widget::{container, image::Handle, lazy, Column, Text},
    Element, Length, Renderer, Subscription, Theme,
};
use url::Url;

@@ -47,13 +49,18 @@ impl Listen {
        }
    }

    pub fn header_magic<'a>(&self, text: Text<'a>) -> Element<'a, Message> {
        header_search(
            Message::OnSearchTerm,
            Message::OnSearchVisibleChange,
            self.search_open,
            &self.search_query,
            text,
    pub fn header_magic(&self, text: Text<'static>) -> Element<'static, Message> {
        lazy(
            (self.search_open, self.search_query.clone()),
            move |(open, query)| {
                header_search(
                    Message::OnSearchTerm,
                    Message::OnSearchVisibleChange,
                    *open,
                    query,
                    text.clone(),
                )
            },
        )
        .into()
    }
@@ -159,13 +166,19 @@ impl Listen {
            }
            Message::OnSearchVisibleChange(v) => {
                self.search_open = v;
                self.search_query = String::new();
                None
            }
        }
    }

    pub fn view(&self) -> Element<'_, Message, Renderer> {
        if let Some((_, speaker)) = self.speaker.clone() {
    pub fn view(&self, style: &Theme) -> Element<'_, Message, Renderer> {
        if self.search_open && !self.search_query.is_empty() {
            container(search::search().view(style))
                .padding([0, 40, 40, 40])
                .width(Length::Fill)
                .into()
        } else if let Some((_, speaker)) = self.speaker.clone() {
            container(
                widgets::media_player::media_player(speaker, self.album_art_image.clone())
                    .with_artist_logo(
diff --git a/shalom/src/pages/room/listen/search.rs b/shalom/src/pages/room/listen/search.rs
new file mode 100644
index 0000000..64d83cc
--- /dev/null
+++ b/shalom/src/pages/room/listen/search.rs
@@ -0,0 +1,102 @@
use iced::{
    theme,
    widget::{
        column, container, container::Appearance, horizontal_rule, image, image::Handle, row,
        scrollable, text, Column,
    },
    Alignment, Background, Color, Element, Length, Renderer, Theme,
};

use crate::{theme::Image, widgets::mouse_area::mouse_area};

pub fn search<M: Clone + 'static>() -> Search<M> {
    Search {
        on_track_press: None,
    }
}

pub struct Search<M> {
    on_track_press: Option<fn(String) -> M>,
}

impl<M: Clone + 'static> Search<M> {
    pub fn view(&self, style: &Theme) -> Element<'static, M, Renderer> {
        let mut col = Column::new();

        for i in 0..20 {
            if i != 0 {
                col = col.push(hr());
            }

            let track = mouse_area(search_item_container(track_card(
                "title",
                "artist",
                Image::AlbumArtTest,
                style,
            )))
            .on_press(self.on_track_press.map(|f| (f)("hello world".to_string())));

            col = col.push(track);
        }

        search_container(scrollable(col.spacing(10)))
    }
}

fn track_card<M: 'static>(
    title: &str,
    artist: &str,
    image_handle: impl Into<Handle>,
    style: &Theme,
) -> Element<'static, M, Renderer> {
    let title = text(title).style(style.extended_palette().background.base.text);
    let artist = text(artist).style(style.extended_palette().background.strong.color);

    row![
        image(image_handle).width(64).height(64),
        column![title, artist,]
    ]
    .align_items(Alignment::Center)
    .spacing(10)
    .into()
}

fn hr<M: 'static>() -> Element<'static, M, Renderer> {
    container(horizontal_rule(1))
        .width(Length::Fill)
        .padding([10, 0, 10, 0])
        .into()
}

fn search_item_container<'a, M: 'a>(
    elem: impl Into<Element<'a, M, Renderer>>,
) -> Element<'a, M, Renderer> {
    container(elem).padding([0, 20, 0, 20]).into()
}

fn search_container<'a, M: 'a>(
    elem: impl Into<Element<'a, M, Renderer>>,
) -> Element<'a, M, Renderer> {
    container(elem)
        .padding([20, 0, 20, 0])
        .width(Length::Fill)
        .style(theme::Container::Custom(Box::new(SearchContainer)))
        .into()
}

#[allow(clippy::module_name_repetitions)]
pub struct SearchContainer;

impl container::StyleSheet for SearchContainer {
    type Style = Theme;

    fn appearance(&self, _style: &Self::Style) -> Appearance {
        Appearance {
            text_color: Some(Color::BLACK),
            background: Some(Background::Color(Color::WHITE)),
            border_radius: 20.0.into(),
            border_width: 0.0,
            border_color: Color::default(),
        }
    }
}
diff --git a/shalom/src/theme.rs b/shalom/src/theme.rs
index a977117..2728ef9 100644
--- a/shalom/src/theme.rs
+++ b/shalom/src/theme.rs
@@ -225,6 +225,7 @@ pub enum Image {
    Bedroom,
    DiningRoom,
    Sunset,
    AlbumArtTest,
}

impl Image {
@@ -249,6 +250,7 @@ impl Image {
            Image::Bedroom => image!("../../assets/images/bedroom.jpg"),
            Image::DiningRoom => image!("../../assets/images/dining_room.jpg"),
            Image::Sunset => image!("../../assets/images/sunset-blur.jpg"),
            Image::AlbumArtTest => image!("../../assets/images/album_art_test.jpg"),
        }
    }

diff --git a/shalom/src/widgets/mouse_area.rs b/shalom/src/widgets/mouse_area.rs
index f6bbb59..a3aae21 100644
--- a/shalom/src/widgets/mouse_area.rs
+++ b/shalom/src/widgets/mouse_area.rs
@@ -43,8 +43,8 @@ pub struct MouseArea<'a, Message, Renderer> {
impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
    /// The message to emit on a left button press.
    #[must_use]
    pub fn on_press(mut self, message: Message) -> Self {
        self.on_press = Some(message);
    pub fn on_press(mut self, message: impl Into<Option<Message>>) -> Self {
        self.on_press = message.into();
        self
    }