Design for enriching search results with metadata
Diff
shalom/src/pages/room/listen.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
shalom/src/pages/room/listen/search.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
2 files changed, 137 insertions(+), 26 deletions(-)
@@ -14,8 +14,9 @@
hass_client::MediaPlayerRepeat,
magic::header_search::header_search,
oracle::{MediaPlayerSpeaker, MediaPlayerSpeakerState, Oracle, Room},
pages::room::listen::search::SearchResult,
subscriptions::{download_image, find_fanart_urls, find_musicbrainz_artist, MaybePendingImage},
theme::{darken_image, trim_transparent_padding},
theme::{darken_image, trim_transparent_padding, Image},
widgets,
};
@@ -174,7 +175,52 @@
pub fn view(&self, style: &Theme) -> Element<'_, Message, Renderer> {
if self.search_open && !self.search_query.is_empty() {
container(search::search().view(style))
let results = vec![
SearchResult::album(Image::AlbumArtTest.into(), "Some Album".to_string()),
SearchResult::track(
Image::AlbumArtTest.into(),
"Some Track".to_string(),
"Some Artist".to_string(),
),
SearchResult::playlist(Image::AlbumArtTest.into(), "Some Playlist".to_string()),
SearchResult::album(Image::AlbumArtTest.into(), "Some Album".to_string()),
SearchResult::track(
Image::AlbumArtTest.into(),
"Some Track".to_string(),
"Some Artist".to_string(),
),
SearchResult::playlist(Image::AlbumArtTest.into(), "Some Playlist".to_string()),
SearchResult::album(Image::AlbumArtTest.into(), "Some Album".to_string()),
SearchResult::track(
Image::AlbumArtTest.into(),
"Some Track".to_string(),
"Some Artist".to_string(),
),
SearchResult::playlist(Image::AlbumArtTest.into(), "Some Playlist".to_string()),
SearchResult::album(Image::AlbumArtTest.into(), "Some Album".to_string()),
SearchResult::track(
Image::AlbumArtTest.into(),
"Some Track".to_string(),
"Some Artist".to_string(),
),
SearchResult::playlist(Image::AlbumArtTest.into(), "Some Playlist".to_string()),
SearchResult::album(Image::AlbumArtTest.into(), "Some Album".to_string()),
SearchResult::track(
Image::AlbumArtTest.into(),
"Some Track".to_string(),
"Some Artist".to_string(),
),
SearchResult::playlist(Image::AlbumArtTest.into(), "Some Playlist".to_string()),
SearchResult::album(Image::AlbumArtTest.into(), "Some Album".to_string()),
SearchResult::track(
Image::AlbumArtTest.into(),
"Some Track".to_string(),
"Some Artist".to_string(),
),
SearchResult::playlist(Image::AlbumArtTest.into(), "Some Playlist".to_string()),
];
container(search::search(style.clone(), results))
.padding([0, 40, 40, 40])
.width(Length::Fill)
.into()
@@ -1,60 +1,76 @@
use std::fmt::{Display, Formatter};
use iced::{
theme,
widget::{
column, container, container::Appearance, horizontal_rule, image, image::Handle, row,
scrollable, text, Column,
column, component, container, container::Appearance, horizontal_rule, image, image::Handle,
row, scrollable, text, Column, Component,
},
Alignment, Background, Color, Element, Length, Renderer, Theme,
};
use crate::{theme::Image, widgets::mouse_area::mouse_area};
use crate::widgets::mouse_area::mouse_area;
pub fn search<M: Clone + 'static>() -> Search<M> {
pub fn search<M: Clone + 'static>(theme: Theme, results: Vec<SearchResult>) -> Search<M> {
Search {
on_track_press: None,
theme,
results,
}
}
pub struct Search<M> {
on_track_press: Option<fn(String) -> M>,
theme: Theme,
results: Vec<SearchResult>,
}
impl<M: Clone + 'static> Component<M, Renderer> for Search<M> {
type State = ();
type Event = Event;
fn update(&mut self, _state: &mut Self::State, event: Self::Event) -> Option<M> {
match event {
Event::OnTrackPress(id) => self.on_track_press.map(|f| (f)(id)),
}
}
impl<M: Clone + 'static> Search<M> {
pub fn view(&self, style: &Theme) -> Element<'static, M, Renderer> {
fn view(&self, _state: &Self::State) -> Element<'_, Self::Event, Renderer> {
let mut col = Column::new();
for i in 0..20 {
for (i, result) in self.results.iter().enumerate() {
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())));
let track = mouse_area(search_item_container(result_card(result, &self.theme)))
.on_press(Event::OnTrackPress("hello world".to_string()));
col = col.push(track);
}
search_container(scrollable(col.spacing(10)))
}
}
impl<M: 'static + Clone> From<Search<M>> for Element<'static, M, Renderer> {
fn from(value: Search<M>) -> Self {
component(value)
}
}
#[derive(Clone)]
pub enum Event {
OnTrackPress(String),
}
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);
fn result_card<M: 'static>(result: &SearchResult, style: &Theme) -> Element<'static, M, Renderer> {
let main_text = text(&result.title).style(style.extended_palette().background.base.text);
let sub_text = text(&result.metadata).style(style.extended_palette().background.strong.color);
row![
image(image_handle).width(64).height(64),
column![title, artist,]
image(result.image.clone()).width(64).height(64),
column![main_text, sub_text,]
]
.align_items(Alignment::Center)
.spacing(10)
@@ -97,6 +113,55 @@
border_radius: 20.0.into(),
border_width: 0.0,
border_color: Color::default(),
}
}
}
#[allow(clippy::module_name_repetitions)]
pub struct SearchResult {
image: Handle,
title: String,
metadata: ResultMetadata,
}
impl SearchResult {
pub fn track(image: Handle, title: String, artist: String) -> Self {
Self {
image,
title,
metadata: ResultMetadata::Track(artist),
}
}
pub fn playlist(image: Handle, title: String) -> Self {
Self {
image,
title,
metadata: ResultMetadata::Playlist,
}
}
pub fn album(image: Handle, title: String) -> Self {
Self {
image,
title,
metadata: ResultMetadata::Album,
}
}
}
pub enum ResultMetadata {
Track(String),
Playlist,
Album,
}
impl Display for ResultMetadata {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ResultMetadata::Track(v) => write!(f, "Track • {v}"),
ResultMetadata::Playlist => write!(f, "Playlist"),
ResultMetadata::Album => write!(f, "Album"),
}
}
}