use std::fmt::Display; use crate::cable::{CableChannelPrototype, CableChannels}; use crate::channels::{CliTvChannel, OnAir, TelevisionChannel, UnitChannel}; use crate::entry::{Entry, PreviewType}; use clap::ValueEnum; use color_eyre::Result; use devicons::FileIcon; use television_fuzzy::matcher::{config::Config, Matcher}; use super::cable; pub struct RemoteControl { matcher: Matcher, cable_channels: Option, } #[derive(Clone)] pub enum RCButton { Channel(UnitChannel), CableChannel(CableChannelPrototype), } impl Display for RCButton { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RCButton::Channel(channel) => write!(f, "{channel}"), RCButton::CableChannel(prototype) => write!(f, "{prototype}"), } } } const NUM_THREADS: usize = 1; impl RemoteControl { pub fn new( builtin_channels: Vec, cable_channels: Option, ) -> Self { let matcher = Matcher::new(Config::default().n_threads(NUM_THREADS)); let injector = matcher.injector(); let buttons = builtin_channels.into_iter().map(RCButton::Channel).chain( cable_channels .as_ref() .map(|channels| { channels.iter().map(|(_, prototype)| { RCButton::CableChannel(prototype.clone()) }) }) .into_iter() .flatten(), ); for button in buttons { let () = injector.push(button.clone(), |e, cols| { cols[0] = e.to_string().clone().into(); }); } RemoteControl { matcher, cable_channels, } } pub fn with_transitions_from( television_channel: &TelevisionChannel, ) -> Self { Self::new(television_channel.available_transitions(), None) } pub fn zap(&self, channel_name: &str) -> Result { if let Ok(channel) = UnitChannel::try_from(channel_name) { Ok(channel.into()) } else { let maybe_prototype = self .cable_channels .as_ref() .and_then(|channels| channels.get(channel_name)); match maybe_prototype { Some(prototype) => Ok(TelevisionChannel::Cable( cable::Channel::from(prototype.clone()), )), None => Err(color_eyre::eyre::eyre!( "No channel or cable channel prototype found for {}", channel_name )), } } } } impl Default for RemoteControl { fn default() -> Self { Self::new( CliTvChannel::value_variants() .iter() .flat_map(|v| UnitChannel::try_from(v.to_string().as_str())) .collect(), None, ) } } pub fn load_builtin_channels() -> Vec { CliTvChannel::value_variants() .iter() .flat_map(|v| UnitChannel::try_from(v.to_string().as_str())) .collect() } const TV_ICON: FileIcon = FileIcon { icon: '📺', color: "#000000", }; const CABLE_ICON: FileIcon = FileIcon { icon: '🍿', color: "#000000", }; impl OnAir for RemoteControl { fn find(&mut self, pattern: &str) { self.matcher.find(pattern); } fn results(&mut self, num_entries: u32, offset: u32) -> Vec { self.matcher.tick(); self.matcher .results(num_entries, offset) .into_iter() .map(|item| { let path = item.matched_string; Entry::new(path, PreviewType::Basic) .with_name_match_ranges(&item.match_indices) .with_icon(match item.inner { RCButton::Channel(_) => TV_ICON, RCButton::CableChannel(_) => CABLE_ICON, }) }) .collect() } fn get_result(&self, index: u32) -> Option { self.matcher.get_result(index).map(|item| { let path = item.matched_string; Entry::new(path, PreviewType::Basic).with_icon(TV_ICON) }) } fn result_count(&self) -> u32 { self.matcher.matched_item_count } fn total_count(&self) -> u32 { self.matcher.total_item_count } fn running(&self) -> bool { self.matcher.status.running } fn shutdown(&self) {} }