Files
zap-stream-app/src/app.rs
2025-01-13 12:13:37 +00:00

199 lines
6.7 KiB
Rust

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<RouteType>,
routes_tx: mpsc::Sender<RouteType>,
#[cfg(target_os = "android")]
app: android_activity::AndroidApp,
widget: Box<dyn NostrWidget>,
profiles: ProfileLoader,
fetch: HashMap<String, Promise<ehttp::Result<ehttp::Response>>>,
}
#[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
}
}
}