use crate::profiles::ProfileLoader; use crate::route::{page, RouteAction, RouteServices, RouteType}; use crate::theme::MARGIN_DEFAULT; use crate::widgets::{Header, NostrWidget}; use eframe::epaint::{FontFamily, Margin}; use eframe::CreationContext; use egui::{Color32, FontData, FontDefinitions, Theme, Ui, Visuals}; use enostr::{PoolEvent, RelayEvent, RelayMessage}; use log::{error, info, warn}; use nostrdb::{Filter, Transaction}; use notedeck::AppContext; use poll_promise::Promise; use std::collections::HashMap; use std::sync::mpsc; pub struct ZapStreamApp { current: RouteType, routes_rx: mpsc::Receiver, routes_tx: mpsc::Sender, #[cfg(target_os = "android")] app: android_activity::AndroidApp, widget: Box, profiles: ProfileLoader, fetch: HashMap>>, } #[cfg(target_os = "android")] impl ZapStreamApp { pub fn new(cc: &CreationContext, app: android_activity::AndroidApp) -> Self { let mut fd = FontDefinitions::default(); fd.font_data.insert( "Outfit".to_string(), FontData::from_static(include_bytes!("../assets/Outfit-Light.ttf")), ); fd.families .insert(FontFamily::Proportional, vec!["Outfit".to_string()]); cc.egui_ctx.set_fonts(fd); let (tx, rx) = mpsc::channel(); Self { current: RouteType::HomePage, widget: Box::new(page::HomePage::new()), profiles: ProfileLoader::new(), routes_tx: tx, routes_rx: rx, app, } } } #[cfg(not(target_os = "android"))] impl ZapStreamApp { pub fn new(cc: &CreationContext) -> Self { let mut fd = FontDefinitions::default(); fd.font_data.insert( "Outfit".to_string(), FontData::from_static(include_bytes!("../assets/Outfit-Light.ttf")), ); fd.families .insert(FontFamily::Proportional, vec!["Outfit".to_string()]); cc.egui_ctx.set_fonts(fd); let (tx, rx) = mpsc::channel(); Self { current: RouteType::HomePage, widget: Box::new(page::HomePage::new()), profiles: ProfileLoader::new(), routes_tx: tx, routes_rx: rx, fetch: HashMap::new(), } } } impl notedeck::App for ZapStreamApp { fn update(&mut self, ctx: &mut AppContext<'_>, ui: &mut Ui) { ctx.accounts.update(ctx.ndb, ctx.pool, ui.ctx()); while let Some(PoolEvent { event, relay }) = ctx.pool.try_recv() { if let RelayEvent::Message(msg) = (&event).into() { match msg { RelayMessage::OK(_) => {} RelayMessage::Eose(_) => {} RelayMessage::Event(_sub, ev) => { if let Err(e) = ctx.ndb.process_event(ev) { error!("Error processing event: {:?}", e); } ui.ctx().request_repaint(); } RelayMessage::Notice(m) => warn!("Notice from {}: {}", relay, m), } } } // reset theme ui.ctx().set_visuals_of( Theme::Dark, Visuals { panel_fill: Color32::BLACK, override_text_color: Some(Color32::WHITE), ..Default::default() }, ); let app_frame = egui::containers::Frame::default().outer_margin(self.frame_margin()); // handle app state changes while let Ok(r) = self.routes_rx.try_recv() { if let RouteType::Action(a) = r { match a { RouteAction::DemandProfile(p) => { self.profiles.demand(p); } _ => info!("Not implemented"), } } else { self.current = r; match &self.current { RouteType::HomePage => { self.widget = Box::new(page::HomePage::new()); } RouteType::EventPage { link, .. } => { self.widget = Box::new(page::StreamPage::new_from_link(link.clone())); } RouteType::LoginPage => { self.widget = Box::new(page::LoginPage::new()); } RouteType::ProfilePage { link } => { self.widget = Box::new(page::ProfilePage::new( link.id.as_bytes().try_into().unwrap(), )); } RouteType::Action { .. } => panic!("Actions!"), _ => panic!("Not implemented"), } } } egui::CentralPanel::default() .frame(app_frame) .show(ui.ctx(), |ui| { let tx = Transaction::new(ctx.ndb).expect("transaction"); // display app ui.vertical(|ui| { let mut svc = RouteServices::new( ui.ctx().clone(), &tx, ctx, self.routes_tx.clone(), &mut self.fetch, ); Header::new().render(ui, &mut svc, &tx); if let Err(e) = self.widget.update(&mut svc) { error!("{}", e); } self.widget.render(ui, &mut svc); }) .response }); let profiles = self.profiles.next(); if !profiles.is_empty() { info!("Profiles: {:?}", profiles); ctx.pool.subscribe( "profiles".to_string(), vec![Filter::new().kinds([0]).authors(&profiles).build()], ); } } } #[cfg(not(target_os = "android"))] impl ZapStreamApp { fn frame_margin(&self) -> Margin { Margin::ZERO } } #[cfg(target_os = "android")] impl ZapStreamApp { fn frame_margin(&self) -> Margin { if let Some(wd) = self.app.native_window() { let (w, h) = (wd.width(), wd.height()); let c_rect = self.app.content_rect(); let dpi = self.app.config().density().unwrap_or(160); let dpi_scale = dpi as f32 / 160.0; // TODO: this calc is weird but seems to work on my phone Margin { bottom: (h - c_rect.bottom) as f32, left: c_rect.left as f32, right: (w - c_rect.right) as f32, top: (c_rect.top - (h - c_rect.bottom)) as f32, } / dpi_scale } else { Margin::ZERO } } }