mod action;
mod event;
mod frame;
mod library;
mod persist;
mod playback;
mod state;
pub(crate) use action::*;
pub(crate) use event::*;
pub(crate) use frame::*;
pub(crate) use library::*;
pub(crate) use persist::*;
pub(crate) use playback::*;
pub(crate) use state::*;
use crate::*;
pub(crate) struct App {
pub(crate) svc: Services,
pub(crate) theme: ThemeState,
pub(crate) playback: PlaybackState,
pub(crate) media_controls: Option<MediaControls>,
#[cfg(all(feature = "mxc", unix))]
pub(crate) mxc: Option<myx::mxc::publish::Publisher>,
pub(crate) status: String,
pub(crate) browse: BrowseState,
pub(crate) transport: Transport,
pub(crate) search: SearchState,
pub(crate) view: ViewState,
pub(crate) session: SessionState,
pub(crate) art_repaint: ArtRepaint,
}
impl App {
pub(crate) fn cur_items(&self) -> &[LibItem] {
if let Some(d) = self.browse.details.last() {
&d.items
} else if self.search.searching {
&self.search.search_results
} else {
self.browse.library.items(self.browse.section)
}
}
pub(crate) fn cur_list_mut(&mut self) -> &mut Vec<LibItem> {
if let Some(d) = self.browse.details.last_mut() {
&mut d.items
} else if self.search.searching {
&mut self.search.search_results
} else {
self.browse.library.items_mut(self.browse.section)
}
}
pub(crate) fn first_selectable(&self) -> usize {
self.cur_items()
.iter()
.position(|i| !i.is_header)
.unwrap_or(0)
}
pub(crate) fn move_sel(&mut self, dir: isize) {
let items = self.cur_items();
let n = items.len() as isize;
if n == 0 {
return;
}
let mut i = self.browse.selected as isize;
loop {
i += dir;
if i < 0 || i >= n {
return;
}
if !items[i as usize].is_header {
self.browse.selected = i as usize;
return;
}
}
}
pub(crate) fn normalize_selection(&mut self) {
if self
.cur_items()
.get(self.browse.selected)
.is_some_and(|i| i.is_header)
{
self.browse.selected = self.first_selectable();
}
}
pub(crate) fn play_context_row(&mut self, uri: String, name: String, shuffle: bool) {
self.status = format!("starting {name}…");
self.transport.source = PlaySource::Context(uri.clone());
self.transport.source_name = name;
if let Err(e) = self.svc.engine.play_context(uri, shuffle) {
self.status = format!("couldn't play: {e:#}");
}
}
pub(crate) fn activate(&mut self) -> Activated {
let Some(item) = self.cur_items().get(self.browse.selected).cloned() else {
return Activated::None;
};
if item.is_header {
return Activated::None;
}
if item.is_play {
if item.uri == "myx:action:liked-play" {
let uris: Vec<String> = self
.browse
.library
.liked
.iter()
.filter(|i| i.is_track)
.map(|i| i.uri.clone())
.collect();
if !uris.is_empty() {
self.transport.source = PlaySource::Liked;
self.transport.source_name = "Liked Songs".to_string();
self.status = "starting Liked Songs…".to_string();
if let Err(e) =
self.svc
.engine
.play_tracks(uris, None, 0, self.transport.shuffle)
{
self.status = format!("couldn't play: {e:#}");
}
}
return Activated::None;
}
let name = self
.browse
.details
.last()
.map(|d| d.title.clone())
.unwrap_or_else(|| item.name.clone());
let shuffle = self.transport.shuffle;
self.play_context_row(item.uri, name, shuffle);
return Activated::None;
}
if item.is_track {
if self.search.searching {
self.transport.source = PlaySource::Radio(item.uri.clone());
self.transport.source_name = format!("Radio · {}", item.name);
return Activated::Radio(item.uri);
}
if let Some(d) = self.browse.details.last() {
let ctx = d.context_uri.clone();
self.transport.source = PlaySource::Context(ctx.clone());
self.transport.source_name = d.title.clone();
self.status = format!("starting {}…", item.name);
if let Err(e) = self.svc.engine.play_context_at(
ctx,
Some(item.uri.clone()),
0,
self.transport.shuffle,
) {
self.status = format!("couldn't play: {e:#}");
}
return Activated::None;
}
let uris = self
.cur_items()
.iter()
.filter(|i| i.is_track)
.map(|i| i.uri.clone())
.collect();
self.status = format!("starting {}…", item.name);
if self.browse.section == Section::Liked {
self.transport.source = PlaySource::Liked;
self.transport.source_name = "Liked Songs".to_string();
} else {
self.transport.source = PlaySource::None;
self.transport.source_name = self.browse.section.label().to_string();
}
if let Err(e) =
self.svc
.engine
.play_tracks(uris, Some(item.uri.clone()), 0, self.transport.shuffle)
{
self.status = format!("couldn't play: {e:#}");
}
return Activated::None;
}
self.status = format!("opening {}…", item.name);
Activated::Open(item.uri, item.name)
}
}