notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit c3bbc6b977d752c4dec036e69336d7edc7cc9aff
parent 8b80096290d8978209ed2276b4ec828cf3b67931
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 13 Dec 2024 08:32:24 -0800

android: fix issues due to rearchitecture

Diffstat:
MCargo.lock | 2+-
Mcrates/notedeck_chrome/Cargo.toml | 1+
Acrates/notedeck_chrome/src/android.rs | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/notedeck_chrome/src/app.rs | 191+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/notedeck_chrome/src/lib.rs | 7+++++++
Mcrates/notedeck_chrome/src/notedeck.rs | 200++-----------------------------------------------------------------------------
Mcrates/notedeck_columns/Cargo.toml | 1-
Mcrates/notedeck_columns/src/lib.rs | 110-------------------------------------------------------------------------------
8 files changed, 306 insertions(+), 308 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -2526,6 +2526,7 @@ dependencies = [ "egui", "egui_extras", "enostr", + "log", "nostrdb", "notedeck", "notedeck_columns", @@ -2557,7 +2558,6 @@ dependencies = [ "hex", "image", "indexmap", - "log", "nostrdb", "notedeck", "open", diff --git a/crates/notedeck_chrome/Cargo.toml b/crates/notedeck_chrome/Cargo.toml @@ -40,6 +40,7 @@ profiling = ["notedeck_columns/puffin"] [target.'cfg(target_os = "android")'.dependencies] android_logger = "0.11.1" +log = { workspace = true } android-activity = { version = "0.4", features = [ "native-activity" ] } winit = { version = "0.30.5", features = [ "android-native-activity" ] } diff --git a/crates/notedeck_chrome/src/android.rs b/crates/notedeck_chrome/src/android.rs @@ -0,0 +1,102 @@ +//#[cfg(target_os = "android")] +//use egui_android::run_android; + +use crate::app::Notedeck; +use winit::platform::android::activity::AndroidApp; +use winit::platform::android::EventLoopBuilderExtAndroid; + +#[no_mangle] +#[tokio::main] +pub async fn android_main(app: AndroidApp) { + std::env::set_var("RUST_BACKTRACE", "full"); + android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info)); + + let path = app.internal_data_path().expect("data path"); + let mut options = eframe::NativeOptions::default(); + options.renderer = eframe::Renderer::Wgpu; + // Clone `app` to use it both in the closure and later in the function + let app_clone_for_event_loop = app.clone(); + options.event_loop_builder = Some(Box::new(move |builder| { + builder.with_android_app(app_clone_for_event_loop); + })); + + let app_args = get_app_args(app); + + let _res = eframe::run_native( + "Damus Notedeck", + options, + Box::new(move |cc| Ok(Box::new(Notedeck::new(&cc.egui_ctx, path, &app_args)))), + ); +} + +use serde_json::Value; +use std::fs; +use std::path::PathBuf; + +/* +Read args from a config file: +- allows use of more interesting args w/o risk of checking them in by mistake +- allows use of different args w/o rebuilding the app +- uses compiled in defaults if config file missing or broken + +Example android-config.json: +``` +{ + "args": [ + "--npub", + "npub1h50pnxqw9jg7dhr906fvy4mze2yzawf895jhnc3p7qmljdugm6gsrurqev", + "-c", + "contacts", + "-c", + "notifications" + ] +} +``` + +Install/update android-config.json with: +``` +adb push android-config.json /sdcard/Android/data/com.damus.app/files/android-config.json +``` + +Using internal storage would be better but it seems hard to get the config file onto +the device ... +*/ + +fn get_app_args(app: AndroidApp) -> Vec<String> { + let external_data_path: PathBuf = app + .external_data_path() + .expect("external data path") + .to_path_buf(); + let config_file = external_data_path.join("android-config.json"); + + let default_args = vec![ + "--pub", + "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", + "-c", + "contacts", + "-c", + "notifications", + "-c", + "notifications:3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", + ] + .into_iter() + .map(|s| s.to_string()) + .collect(); + + if config_file.exists() { + if let Ok(config_contents) = fs::read_to_string(config_file) { + if let Ok(json) = serde_json::from_str::<Value>(&config_contents) { + if let Some(args_array) = json.get("args").and_then(|v| v.as_array()) { + let config_args = args_array + .iter() + .filter_map(|v| v.as_str().map(String::from)) + .collect(); + + return config_args; + } + } + } + } + + default_args // Return the default args if config is missing or invalid +} diff --git a/crates/notedeck_chrome/src/app.rs b/crates/notedeck_chrome/src/app.rs @@ -0,0 +1,191 @@ +use crate::{app_size::AppSizeHandler, setup::setup_cc, theme}; + +use notedeck::{ + Accounts, AppContext, Args, DataPath, DataPathType, Directory, FileKeyStorage, ImageCache, + KeyStorageType, NoteCache, ThemeHandler, UnknownIds, +}; + +use enostr::RelayPool; +use nostrdb::{Config, Ndb, Transaction}; +use std::cell::RefCell; +use std::path::Path; +use std::rc::Rc; +use tracing::info; + +/// Our browser app state +pub struct Notedeck { + ndb: Ndb, + img_cache: ImageCache, + unknown_ids: UnknownIds, + pool: RelayPool, + note_cache: NoteCache, + accounts: Accounts, + path: DataPath, + args: Args, + theme: ThemeHandler, + tabs: Tabs, + app_rect_handler: AppSizeHandler, + egui: egui::Context, +} + +impl eframe::App for Notedeck { + /// Called by the frame work to save state before shutdown. + fn save(&mut self, _storage: &mut dyn eframe::Storage) { + //eframe::set_value(storage, eframe::APP_KEY, self); + } + + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // TODO: render chrome + + // render app + if let Some(app) = &self.tabs.app { + let app = app.clone(); + app.borrow_mut().update(&mut self.app_context()); + } + + self.app_rect_handler.try_save_app_size(ctx); + } +} + +impl Notedeck { + pub fn new<P: AsRef<Path>>(ctx: &egui::Context, data_path: P, args: &[String]) -> Self { + let parsed_args = Args::parse(args); + let is_mobile = parsed_args + .is_mobile + .unwrap_or(notedeck::ui::is_compiled_as_mobile()); + + // Some people have been running notedeck in debug, let's catch that! + if !cfg!(test) && cfg!(debug_assertions) && !parsed_args.debug { + println!("--- WELCOME TO DAMUS NOTEDECK! ---"); + println!("It looks like are running notedeck in debug mode, unless you are a developer, this is not likely what you want."); + println!("If you are a developer, run `cargo run -- --debug` to skip this message."); + println!("For everyone else, try again with `cargo run --release`. Enjoy!"); + println!("---------------------------------"); + panic!(); + } + + setup_cc(ctx, is_mobile, parsed_args.light); + + let data_path = parsed_args + .datapath + .unwrap_or(data_path.as_ref().to_str().expect("db path ok").to_string()); + let path = DataPath::new(&data_path); + let dbpath_str = parsed_args + .dbpath + .unwrap_or_else(|| path.path(DataPathType::Db).to_str().unwrap().to_string()); + + let _ = std::fs::create_dir_all(&dbpath_str); + + let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir()); + let _ = std::fs::create_dir_all(imgcache_dir.clone()); + + let mapsize = if cfg!(target_os = "windows") { + // 16 Gib on windows because it actually creates the file + 1024usize * 1024usize * 1024usize * 16usize + } else { + // 1 TiB for everything else since its just virtually mapped + 1024usize * 1024usize * 1024usize * 1024usize + }; + + let theme = ThemeHandler::new(&path); + ctx.options_mut(|o| { + let cur_theme = theme.load(); + info!("Loaded theme {:?} from disk", cur_theme); + o.theme_preference = cur_theme; + }); + ctx.set_visuals_of( + egui::Theme::Dark, + theme::dark_mode(notedeck::ui::is_compiled_as_mobile()), + ); + ctx.set_visuals_of(egui::Theme::Light, theme::light_mode()); + + let config = Config::new().set_ingester_threads(4).set_mapsize(mapsize); + + let keystore = if parsed_args.use_keystore { + let keys_path = path.path(DataPathType::Keys); + let selected_key_path = path.path(DataPathType::SelectedKey); + KeyStorageType::FileSystem(FileKeyStorage::new( + Directory::new(keys_path), + Directory::new(selected_key_path), + )) + } else { + KeyStorageType::None + }; + + let mut accounts = Accounts::new(keystore, parsed_args.relays); + + let num_keys = parsed_args.keys.len(); + + let mut unknown_ids = UnknownIds::default(); + let ndb = Ndb::new(&dbpath_str, &config).expect("ndb"); + + { + let txn = Transaction::new(&ndb).expect("txn"); + for key in parsed_args.keys { + info!("adding account: {}", key.pubkey); + accounts + .add_account(key) + .process_action(&mut unknown_ids, &ndb, &txn); + } + } + + if num_keys != 0 { + accounts.select_account(0); + } + + // AccountManager will setup the pool on first update + let pool = RelayPool::new(); + + let img_cache = ImageCache::new(imgcache_dir); + let note_cache = NoteCache::default(); + let unknown_ids = UnknownIds::default(); + let egui = ctx.clone(); + let tabs = Tabs::new(None); + let parsed_args = Args::parse(args); + let app_rect_handler = AppSizeHandler::new(&path); + + Self { + ndb, + img_cache, + app_rect_handler, + unknown_ids, + pool, + note_cache, + accounts, + path: path.clone(), + args: parsed_args, + theme, + egui, + tabs, + } + } + + pub fn app_context(&mut self) -> AppContext<'_> { + AppContext { + ndb: &self.ndb, + img_cache: &mut self.img_cache, + unknown_ids: &mut self.unknown_ids, + pool: &mut self.pool, + note_cache: &mut self.note_cache, + accounts: &mut self.accounts, + path: &self.path, + args: &self.args, + theme: &mut self.theme, + egui: &self.egui, + } + } + + pub fn add_app<T: notedeck::App + 'static>(&mut self, app: T) { + self.tabs.app = Some(Rc::new(RefCell::new(app))); + } +} + +struct Tabs { + app: Option<Rc<RefCell<dyn notedeck::App>>>, +} + +impl Tabs { + pub fn new(app: Option<Rc<RefCell<dyn notedeck::App>>>) -> Self { + Self { app } + } +} diff --git a/crates/notedeck_chrome/src/lib.rs b/crates/notedeck_chrome/src/lib.rs @@ -2,3 +2,10 @@ pub mod app_size; pub mod fonts; pub mod setup; pub mod theme; + +mod app; + +pub use app::Notedeck; + +#[cfg(target_os = "android")] +mod android; diff --git a/crates/notedeck_chrome/src/notedeck.rs b/crates/notedeck_chrome/src/notedeck.rs @@ -1,204 +1,12 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use notedeck_chrome::{ - app_size::AppSizeHandler, - setup::{generate_native_options, setup_cc}, - theme, -}; +use notedeck_chrome::{setup::generate_native_options, Notedeck}; +use notedeck::{DataPath, DataPathType}; use notedeck_columns::Damus; - -use notedeck::{ - Accounts, AppContext, Args, DataPath, DataPathType, Directory, FileKeyStorage, ImageCache, - KeyStorageType, NoteCache, ThemeHandler, UnknownIds, -}; - -use enostr::RelayPool; -use nostrdb::{Config, Ndb, Transaction}; -use std::cell::RefCell; -use std::path::Path; -use std::rc::Rc; -use std::{path::PathBuf, str::FromStr}; -use tracing::info; +use std::path::PathBuf; +use std::str::FromStr; use tracing_subscriber::EnvFilter; -/// Our browser app state -struct Notedeck { - ndb: Ndb, - img_cache: ImageCache, - unknown_ids: UnknownIds, - pool: RelayPool, - note_cache: NoteCache, - accounts: Accounts, - path: DataPath, - args: Args, - theme: ThemeHandler, - tabs: Tabs, - app_rect_handler: AppSizeHandler, - egui: egui::Context, -} - -struct Tabs { - app: Option<Rc<RefCell<dyn notedeck::App>>>, -} - -impl Tabs { - pub fn new(app: Option<Rc<RefCell<dyn notedeck::App>>>) -> Self { - Self { app } - } -} - -impl eframe::App for Notedeck { - /// Called by the frame work to save state before shutdown. - fn save(&mut self, _storage: &mut dyn eframe::Storage) { - //eframe::set_value(storage, eframe::APP_KEY, self); - } - - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - // TODO: render chrome - - // render app - if let Some(app) = &self.tabs.app { - let app = app.clone(); - app.borrow_mut().update(&mut self.app_context()); - } - - self.app_rect_handler.try_save_app_size(ctx); - } -} - -impl Notedeck { - pub fn new<P: AsRef<Path>>(ctx: &egui::Context, data_path: P, args: &[String]) -> Self { - let parsed_args = Args::parse(args); - let is_mobile = parsed_args - .is_mobile - .unwrap_or(notedeck::ui::is_compiled_as_mobile()); - - // Some people have been running notedeck in debug, let's catch that! - if !cfg!(test) && cfg!(debug_assertions) && !parsed_args.debug { - println!("--- WELCOME TO DAMUS NOTEDECK! ---"); - println!("It looks like are running notedeck in debug mode, unless you are a developer, this is not likely what you want."); - println!("If you are a developer, run `cargo run -- --debug` to skip this message."); - println!("For everyone else, try again with `cargo run --release`. Enjoy!"); - println!("---------------------------------"); - panic!(); - } - - setup_cc(ctx, is_mobile, parsed_args.light); - - let data_path = parsed_args - .datapath - .unwrap_or(data_path.as_ref().to_str().expect("db path ok").to_string()); - let path = DataPath::new(&data_path); - let dbpath_str = parsed_args - .dbpath - .unwrap_or_else(|| path.path(DataPathType::Db).to_str().unwrap().to_string()); - - let _ = std::fs::create_dir_all(&dbpath_str); - - let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir()); - let _ = std::fs::create_dir_all(imgcache_dir.clone()); - - let mapsize = if cfg!(target_os = "windows") { - // 16 Gib on windows because it actually creates the file - 1024usize * 1024usize * 1024usize * 16usize - } else { - // 1 TiB for everything else since its just virtually mapped - 1024usize * 1024usize * 1024usize * 1024usize - }; - - let theme = ThemeHandler::new(&path); - ctx.options_mut(|o| { - let cur_theme = theme.load(); - info!("Loaded theme {:?} from disk", cur_theme); - o.theme_preference = cur_theme; - }); - ctx.set_visuals_of( - egui::Theme::Dark, - theme::dark_mode(notedeck::ui::is_compiled_as_mobile()), - ); - ctx.set_visuals_of(egui::Theme::Light, theme::light_mode()); - - let config = Config::new().set_ingester_threads(4).set_mapsize(mapsize); - - let keystore = if parsed_args.use_keystore { - let keys_path = path.path(DataPathType::Keys); - let selected_key_path = path.path(DataPathType::SelectedKey); - KeyStorageType::FileSystem(FileKeyStorage::new( - Directory::new(keys_path), - Directory::new(selected_key_path), - )) - } else { - KeyStorageType::None - }; - - let mut accounts = Accounts::new(keystore, parsed_args.relays); - - let num_keys = parsed_args.keys.len(); - - let mut unknown_ids = UnknownIds::default(); - let ndb = Ndb::new(&dbpath_str, &config).expect("ndb"); - - { - let txn = Transaction::new(&ndb).expect("txn"); - for key in parsed_args.keys { - info!("adding account: {}", key.pubkey); - accounts - .add_account(key) - .process_action(&mut unknown_ids, &ndb, &txn); - } - } - - if num_keys != 0 { - accounts.select_account(0); - } - - // AccountManager will setup the pool on first update - let pool = RelayPool::new(); - - let img_cache = ImageCache::new(imgcache_dir); - let note_cache = NoteCache::default(); - let unknown_ids = UnknownIds::default(); - let egui = ctx.clone(); - let tabs = Tabs::new(None); - let parsed_args = Args::parse(args); - let app_rect_handler = AppSizeHandler::new(&path); - - Self { - ndb, - img_cache, - app_rect_handler, - unknown_ids, - pool, - note_cache, - accounts, - path: path.clone(), - args: parsed_args, - theme, - egui, - tabs, - } - } - - pub fn app_context(&mut self) -> AppContext<'_> { - AppContext { - ndb: &self.ndb, - img_cache: &mut self.img_cache, - unknown_ids: &mut self.unknown_ids, - pool: &mut self.pool, - note_cache: &mut self.note_cache, - accounts: &mut self.accounts, - path: &self.path, - args: &self.args, - theme: &mut self.theme, - egui: &self.egui, - } - } - - pub fn add_app<T: notedeck::App + 'static>(&mut self, app: T) { - self.tabs.app = Some(Rc::new(RefCell::new(app))); - } -} - // Entry point for wasm //#[cfg(target_arch = "wasm32")] //use wasm_bindgen::prelude::*; diff --git a/crates/notedeck_columns/Cargo.toml b/crates/notedeck_columns/Cargo.toml @@ -27,7 +27,6 @@ env_logger = { workspace = true } hex = { workspace = true } image = { workspace = true } indexmap = { workspace = true } -log = { workspace = true } nostrdb = { workspace = true } open = { workspace = true } poll-promise = { workspace = true } diff --git a/crates/notedeck_columns/src/lib.rs b/crates/notedeck_columns/src/lib.rs @@ -44,114 +44,4 @@ pub use app::Damus; pub use error::Error; pub use profile::DisplayName; -#[cfg(target_os = "android")] -use winit::platform::android::EventLoopBuilderExtAndroid; - pub type Result<T> = std::result::Result<T, error::Error>; - -//#[cfg(target_os = "android")] -//use egui_android::run_android; - -#[cfg(target_os = "android")] -use winit::platform::android::activity::AndroidApp; - -#[cfg(target_os = "android")] -#[no_mangle] -#[tokio::main] -pub async fn android_main(app: AndroidApp) { - std::env::set_var("RUST_BACKTRACE", "full"); - android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info)); - - let path = app.internal_data_path().expect("data path"); - let mut options = eframe::NativeOptions::default(); - options.renderer = eframe::Renderer::Wgpu; - // Clone `app` to use it both in the closure and later in the function - let app_clone_for_event_loop = app.clone(); - options.event_loop_builder = Some(Box::new(move |builder| { - builder.with_android_app(app_clone_for_event_loop); - })); - - let app_args = get_app_args(app); - - let _res = eframe::run_native( - "Damus Notedeck", - options, - Box::new(move |cc| Ok(Box::new(Damus::new(&cc.egui_ctx, path, app_args)))), - ); -} - -#[cfg(target_os = "android")] -use serde_json::Value; -#[cfg(target_os = "android")] -use std::fs; -#[cfg(target_os = "android")] -use std::path::PathBuf; - -/* -Read args from a config file: -- allows use of more interesting args w/o risk of checking them in by mistake -- allows use of different args w/o rebuilding the app -- uses compiled in defaults if config file missing or broken - -Example android-config.json: -``` -{ - "args": [ - "--npub", - "npub1h50pnxqw9jg7dhr906fvy4mze2yzawf895jhnc3p7qmljdugm6gsrurqev", - "-c", - "contacts", - "-c", - "notifications" - ] -} -``` - -Install/update android-config.json with: -``` -adb push android-config.json /sdcard/Android/data/com.damus.app/files/android-config.json -``` - -Using internal storage would be better but it seems hard to get the config file onto -the device ... -*/ - -#[cfg(target_os = "android")] -fn get_app_args(app: AndroidApp) -> Vec<String> { - let external_data_path: PathBuf = app - .external_data_path() - .expect("external data path") - .to_path_buf(); - let config_file = external_data_path.join("android-config.json"); - - let default_args = vec![ - "--pub", - "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", - "-c", - "contacts", - "-c", - "notifications", - "-c", - "notifications:3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", - ] - .into_iter() - .map(|s| s.to_string()) - .collect(); - - if config_file.exists() { - if let Ok(config_contents) = fs::read_to_string(config_file) { - if let Ok(json) = serde_json::from_str::<Value>(&config_contents) { - if let Some(args_array) = json.get("args").and_then(|v| v.as_array()) { - let config_args = args_array - .iter() - .filter_map(|v| v.as_str().map(String::from)) - .collect(); - - return config_args; - } - } - } - } - - default_args // Return the default args if config is missing or invalid -}