| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use std::{fs::File, io::Write};
- use darkfi::system::msleep;
- use darkfi_serial::{deserialize, Encodable};
- use indoc::indoc;
- use kvdb_overlay::Database;
- use crate::{
- app::{
- node::{create_button, create_layer, create_text, create_vector_art, create_video},
- App,
- },
- expr::{self, Compiler},
- gfx::gfxtag,
- prop::{PropertyAtomicGuard, PropertyFloat32, Role},
- scene::{SceneNodePtr, Slot},
- sfx, shape,
- ui::{emoji_picker, Button, Layer, Text, VectorArt, VectorShape, Video},
- util::i18n::I18nBabelFish,
- };
- mod chat;
- pub mod menu;
- use menu::channel::Channel;
- pub mod test;
- pub mod test_edit;
- pub mod test_scroll_layer;
- mod wallet;
- macro_rules! i { ($($arg:tt)*) => { info!(target: "app::schema", $($arg)*); } }
- macro_rules! e { ($($arg:tt)*) => { error!(target: "app::schema", $($arg)*); } }
- const COLOR_SCHEME: ColorScheme = ColorScheme::DarkMode;
- //const COLOR_SCHEME: ColorScheme = ColorScheme::PaperLight;
- #[cfg(any(target_os = "android", feature = "emulate-android"))]
- mod android_ui_consts {
- pub const NETSTATUS_ICON_SIZE: f32 = 140.;
- pub const SETTINGS_ICON_SIZE: f32 = 140.;
- pub const NETLOGO_SCALE: f32 = 50.;
- pub const EMOJI_PICKER_ICON_SIZE: f32 = 120.;
- pub const EMOJI_PICKER_ICON_MARGIN_X: f32 = 20.;
- pub const EMOJI_PICKER_ICON_MARGIN_Y: f32 = 20.;
- pub const NETSTAT_OVERLAY_MARGIN: f32 = 20.;
- pub const NETSTAT_OVERLAY_BTN_W: f32 = 200.;
- pub const NETSTAT_OVERLAY_BTN_H: f32 = 90.;
- pub const NETSTAT_OVERLAY_BTN_FONTSIZE: f32 = 40.;
- }
- #[cfg(target_os = "android")]
- mod ui_consts {
- use crate::android::{get_appdata_path, get_external_storage_path};
- use std::path::PathBuf;
- pub const VID_PATH: &str = "forest_720x1280.mp4";
- pub const VID_ASPECT_RATIO: f32 = 9. / 16.;
- pub use super::android_ui_consts::*;
- pub fn get_chatdb_path() -> PathBuf {
- get_external_storage_path().join("chatdb")
- }
- pub fn get_main_db_path() -> PathBuf {
- get_appdata_path().join("db")
- }
- pub fn get_joined_channels_filename() -> PathBuf {
- get_appdata_path().join("joined.txt")
- }
- }
- #[cfg(not(target_os = "android"))]
- mod desktop_paths {
- use std::path::PathBuf;
- pub const VID_PATH: &str = "assets/forest_1920x1080.ivf";
- pub const VID_ASPECT_RATIO: f32 = 16. / 9.;
- pub fn get_chatdb_path() -> PathBuf {
- dirs::data_local_dir().unwrap().join("darkfi/app/chatdb")
- }
- pub fn get_main_db_path() -> PathBuf {
- dirs::data_local_dir().unwrap().join("darkfi/app/db")
- }
- pub fn get_joined_channels_filename() -> PathBuf {
- dirs::cache_dir().unwrap().join("darkfi/app/joined.txt")
- }
- }
- #[cfg(feature = "emulate-android")]
- mod ui_consts {
- pub use super::{android_ui_consts::*, desktop_paths::*};
- }
- #[cfg(all(
- any(target_os = "linux", target_os = "macos", target_os = "windows"),
- not(feature = "emulate-android")
- ))]
- mod ui_consts {
- pub const NETSTATUS_ICON_SIZE: f32 = 60.;
- pub const SETTINGS_ICON_SIZE: f32 = 60.;
- pub const NETLOGO_SCALE: f32 = 25.;
- pub const EMOJI_PICKER_ICON_SIZE: f32 = 40.;
- pub const EMOJI_PICKER_ICON_MARGIN_X: f32 = 8.;
- pub const EMOJI_PICKER_ICON_MARGIN_Y: f32 = 8.;
- pub const NETSTAT_OVERLAY_MARGIN: f32 = 10.;
- pub const NETSTAT_OVERLAY_BTN_W: f32 = 100.;
- pub const NETSTAT_OVERLAY_BTN_H: f32 = 45.;
- pub const NETSTAT_OVERLAY_BTN_FONTSIZE: f32 = 20.;
- pub use super::desktop_paths::*;
- }
- pub use ui_consts::*;
- pub static DEFAULT_CHANNELS: &'static [&str] =
- &["dev", "media", "hackers", "memes", "philosophy", "markets", "math", "random"];
- #[derive(PartialEq)]
- enum ColorScheme {
- DarkMode,
- PaperLight,
- }
- /// Read the ordered list of joined channels/contacts (prefixed names like "#dev", "@alice").
- pub fn read_joined_channels() -> Vec<String> {
- let Ok(contents) = std::fs::read_to_string(get_joined_channels_filename()) else {
- return vec![]
- };
- let mut joined = vec![];
- for line in contents.lines() {
- let line = line.trim();
- assert!(!line.is_empty());
- joined.push(line.to_string());
- }
- joined
- }
- /// First-run seed: write DEFAULT_CHANNELS (as "#name") if no joined file exists.
- /// Idempotent and deterministic, so it is safe to call from both schema startup and the plugin.
- pub fn ensure_joined_channels_seeded() {
- let path = get_joined_channels_filename();
- if path.exists() {
- return
- }
- if let Some(parent) = path.parent() {
- let _ = std::fs::create_dir_all(parent);
- }
- let defaults: Vec<String> = DEFAULT_CHANNELS.iter().map(|c| format!("#{}", c)).collect();
- let _ = std::fs::write(&path, defaults.join("\n"));
- }
- /// Append a single channel line to the joined file (no dedup). Ensures newline separation
- /// when appending to a non-empty file that lacks a trailing newline.
- pub fn write_joined_channel(name: &str) {
- let path = get_joined_channels_filename();
- let needs_newline = std::fs::read(&path)
- .map(|bytes| !bytes.is_empty() && !bytes.ends_with(b"\n"))
- .unwrap_or(false);
- let mut f = std::fs::OpenOptions::new().create(true).append(true).open(&path).unwrap();
- if needs_newline {
- writeln!(f).unwrap();
- }
- writeln!(f, "{}", name).unwrap();
- }
- /// Join a channel: skip if already joined, otherwise append one line.
- pub fn append_joined_channel(name: &str) {
- if read_joined_channels().iter().any(|x| x == name) {
- return
- }
- write_joined_channel(name);
- }
- /// Rewrite the whole joined list (used for first-run seed and main-menu edit_done sync).
- pub fn write_joined_channels(items: &[String]) {
- let _ = std::fs::write(get_joined_channels_filename(), items.join("\n"));
- }
- pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish, db: Database) {
- let mut cc = Compiler::new();
- cc.add_const_f32("NETSTATUS_ICON_SIZE", NETSTATUS_ICON_SIZE);
- cc.add_const_f32("SETTINGS_ICON_SIZE", SETTINGS_ICON_SIZE);
- cc.add_const_f32("NETSTAT_OVERLAY_MARGIN", NETSTAT_OVERLAY_MARGIN);
- cc.add_const_f32("NETSTAT_OVERLAY_BTN_W", NETSTAT_OVERLAY_BTN_W);
- cc.add_const_f32("NETSTAT_OVERLAY_BTN_H", NETSTAT_OVERLAY_BTN_H);
- let atom = &mut PropertyAtomicGuard::none();
- let window_scale = PropertyFloat32::wrap(
- &app.sg_root.lookup_node("/window").unwrap(),
- Role::Internal,
- "scale",
- 0,
- )
- .unwrap();
- // Root content layer
- let content = create_layer("content");
- let prop = content.get_property("rect").unwrap();
- prop.set_expr(atom, Role::App, 0, expr::load_var("insets_left")).unwrap();
- prop.set_expr(atom, Role::App, 1, expr::load_var("insets_top")).unwrap();
- let code = cc.compile("w - insets_left - insets_right").unwrap();
- prop.set_expr(atom, Role::App, 2, code).unwrap();
- let code = cc.compile("h - insets_top - insets_bottom").unwrap();
- prop.set_expr(atom, Role::App, 3, code).unwrap();
- let window_insets = window.get_property("insets").unwrap();
- prop.add_depend(&window_insets, 0, "insets_left");
- prop.add_depend(&window_insets, 1, "insets_top");
- prop.add_depend(&window_insets, 2, "insets_right");
- prop.add_depend(&window_insets, 3, "insets_bottom");
- content.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
- content.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
- let content =
- content.setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
- window.link(content.clone());
- if COLOR_SCHEME == ColorScheme::DarkMode {
- let node = create_video("king");
- let prop = node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, 0.).unwrap();
- prop.set_f32(atom, Role::App, 1, 0.).unwrap();
- prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
- prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
- cc.add_const_f32("R", VID_ASPECT_RATIO);
- let prop = node.get_property("uv").unwrap();
- #[rustfmt::skip]
- let code = cc.compile(indoc! {"
- r = w / h;
- # r < R means screen narrower than image
- if r < R {
- 0.5 - (r / (2 * R))
- } else {
- 0
- }
- "}).unwrap();
- prop.set_expr(atom, Role::App, 0, code).unwrap();
- #[rustfmt::skip]
- let code = cc.compile(indoc! {"
- r = w / h;
- if r < R {
- 0
- } else {
- 0.5 - (R / (2 * r))
- }
- "}).unwrap();
- prop.set_expr(atom, Role::App, 1, code).unwrap();
- #[rustfmt::skip]
- let code = cc.compile(indoc! {"
- r = w / h;
- if r < R {
- r / R
- } else {
- 1
- }
- "}).unwrap();
- prop.set_expr(atom, Role::App, 2, code).unwrap();
- #[rustfmt::skip]
- let code = cc.compile(indoc! {"
- r = w / h;
- if r < R {
- 1
- } else {
- R / r
- }
- "}).unwrap();
- prop.set_expr(atom, Role::App, 3, code).unwrap();
- //node.set_property_str(atom, Role::App, "path", BG_PATH).unwrap();
- node.set_property_str(atom, Role::App, "path", VID_PATH).unwrap();
- node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
- //let node = node.setup(|me| Image::new(me, app.renderer.clone())).await;
- //layer_node.link(node);
- let node = node
- .setup(|me| {
- Video::new(me, app.renderer.clone(), app.redraw_trigger.clone(), app.ex.clone())
- })
- .await;
- content.link(node);
- } else if COLOR_SCHEME == ColorScheme::PaperLight {
- let node = create_vector_art("bg");
- let prop = node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, 0.).unwrap();
- prop.set_f32(atom, Role::App, 1, 0.).unwrap();
- prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
- prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
- node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
- let c = 1.;
- // Setup the pimpl
- let mut shape = VectorShape::new();
- shape.add_filled_box(
- expr::const_f32(0.),
- expr::const_f32(0.),
- expr::load_var("w"),
- expr::load_var("h"),
- [c, c, c, 0.3],
- );
- let node = node
- .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
- .await;
- window.link(node);
- }
- let emoji_meshes = emoji_picker::EmojiMeshes::new(app.renderer.clone(), EMOJI_PICKER_ICON_SIZE);
- let emoji_meshes2 = emoji_meshes.clone();
- let _ = std::thread::spawn(move || {
- for i in (0..500).step_by(20) {
- for j in i..(i + 20) {
- emoji_meshes2.lock().get(j);
- }
- }
- });
- let channels_tree = db.open_tree_default("channels").expect("cannot open channels tree");
- let contacts_tree = db.open_tree_default("contacts").expect("cannot open contacts tree");
- // Initialize default channels if tree is empty
- if channels_tree.is_empty().unwrap() {
- for channel_name in DEFAULT_CHANNELS {
- let channel = Channel { name: channel_name.to_string(), secret: None };
- let mut val = vec![];
- channel.encode(&mut val).unwrap();
- channels_tree.insert(channel_name.to_string().as_bytes(), &val).unwrap();
- }
- }
- // Seed the joined-channels file with defaults on first run.
- ensure_joined_channels_seeded();
- // Create chat container layer
- let chat_layer = create_layer("chat");
- let prop = chat_layer.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, 0.).unwrap();
- prop.set_f32(atom, Role::App, 1, 0.).unwrap();
- prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
- prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
- chat_layer.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
- chat_layer.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
- let chat_layer = chat_layer
- .setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
- .await;
- content.link(chat_layer.clone());
- let netlayer_node = create_layer("netstatus_layer");
- let prop = netlayer_node.get_property("rect").unwrap();
- let code = cc.compile("w - NETSTATUS_ICON_SIZE").unwrap();
- prop.set_expr(atom, Role::App, 0, code).unwrap();
- prop.set_f32(atom, Role::App, 1, 0.).unwrap();
- //prop.set_f32(atom, Role::App, 2, NETSTATUS_ICON_SIZE).unwrap();
- //prop.set_f32(atom, Role::App, 3, NETSTATUS_ICON_SIZE).unwrap();
- prop.set_f32(atom, Role::App, 2, 1000.).unwrap();
- prop.set_f32(atom, Role::App, 3, 1000.).unwrap();
- netlayer_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
- netlayer_node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
- netlayer_node.set_property_u32(atom, Role::App, "priority", 1).unwrap();
- let netlayer_node = netlayer_node
- .setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
- .await;
- chat_layer.link(netlayer_node.clone());
- let node = create_vector_art("net0");
- let prop = node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, NETSTATUS_ICON_SIZE / 2.).unwrap();
- prop.set_f32(atom, Role::App, 1, NETSTATUS_ICON_SIZE / 2.).unwrap();
- prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
- prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
- node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
- node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
- node.set_property_f32(atom, Role::App, "scale", NETLOGO_SCALE).unwrap();
- let mut shape = shape::create_netlogo1([1., 0., 0.25, 1.]);
- shape.join(shape::create_netlogo2([0.27, 0.4, 0.4, 1.]));
- shape.join(shape::create_netlogo3([0.27, 0.4, 0.4, 1.]));
- let net0_node = node
- .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
- .await;
- netlayer_node.link(net0_node);
- let node = create_vector_art("net1");
- let prop = node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, NETSTATUS_ICON_SIZE / 2.).unwrap();
- prop.set_f32(atom, Role::App, 1, NETSTATUS_ICON_SIZE / 2.).unwrap();
- prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
- prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
- node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
- node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
- node.set_property_f32(atom, Role::App, "scale", NETLOGO_SCALE).unwrap();
- let mut shape = shape::create_netlogo1([0.49, 0.57, 1., 1.]);
- shape.join(shape::create_netlogo2([0.49, 0.57, 1., 1.]));
- shape.join(shape::create_netlogo3([0.27, 0.4, 0.4, 1.]));
- let net1_node = node
- .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
- .await;
- netlayer_node.link(net1_node);
- let node = create_vector_art("net2");
- let prop = node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, NETSTATUS_ICON_SIZE / 2.).unwrap();
- prop.set_f32(atom, Role::App, 1, NETSTATUS_ICON_SIZE / 2.).unwrap();
- prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
- prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
- node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
- node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
- node.set_property_f32(atom, Role::App, "scale", NETLOGO_SCALE).unwrap();
- let mut shape = shape::create_netlogo1([0., 0.94, 1., 1.]);
- shape.join(shape::create_netlogo2([0., 0.94, 1., 1.]));
- shape.join(shape::create_netlogo3([0., 0.94, 1., 1.]));
- let net2_node = node
- .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
- .await;
- netlayer_node.link(net2_node);
- let node = create_vector_art("net3");
- let prop = node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, NETSTATUS_ICON_SIZE / 2.).unwrap();
- prop.set_f32(atom, Role::App, 1, NETSTATUS_ICON_SIZE / 2.).unwrap();
- prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
- prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
- node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
- node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
- node.set_property_f32(atom, Role::App, "scale", NETLOGO_SCALE).unwrap();
- let mut shape = shape::create_netlogo1([0., 0.94, 1., 1.]);
- shape.join(shape::create_netlogo2([0., 0.94, 1., 1.]));
- shape.join(shape::create_netlogo3([0., 0.94, 1., 1.]));
- let net3_node = node
- .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
- .await;
- netlayer_node.link(net3_node);
- // netstat-klik icon (visual feedback when reconnect button is clicked)
- let klik_color = [0., 0.5, 1., 1.]; // Blue
- let node = create_vector_art("netstat_klik");
- let prop = node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, 0.).unwrap();
- prop.set_f32(atom, Role::App, 1, 0.).unwrap();
- prop.set_f32(atom, Role::App, 2, NETSTATUS_ICON_SIZE).unwrap();
- prop.set_f32(atom, Role::App, 3, NETSTATUS_ICON_SIZE).unwrap();
- node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
- // Above other icons
- node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
- let mut shape = VectorShape::new();
- shape.add_filled_box(
- expr::const_f32(0.),
- expr::const_f32(0.),
- expr::const_f32(NETSTATUS_ICON_SIZE),
- expr::const_f32(NETSTATUS_ICON_SIZE),
- klik_color,
- );
- let netstat_klik_node = node
- .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
- .await;
- netlayer_node.link(netstat_klik_node.clone());
- // Reconnect Button (overlaid on netstatus icons)
- let node = create_button("reconnect_btn");
- node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
- let prop = node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, 0.).unwrap();
- prop.set_f32(atom, Role::App, 1, 0.).unwrap();
- prop.set_f32(atom, Role::App, 2, NETSTATUS_ICON_SIZE).unwrap();
- prop.set_f32(atom, Role::App, 3, NETSTATUS_ICON_SIZE).unwrap();
- let sg_root = app.sg_root.clone();
- let redraw = app.redraw_trigger.clone();
- let (slot, recvr) = Slot::new("reconnect_clicked");
- node.register("click", slot).unwrap();
- let reconnect_task = app.ex.spawn(async move {
- while let Ok(_) = recvr.recv().await {
- i!("Reconnect button clicked");
- // Toggle the overlay layer
- let overlay = sg_root.lookup_node("/window/content/chat/netstatus_overlay").unwrap();
- let is_visible = overlay.get_property_bool("is_visible").unwrap();
- if !is_visible {
- sfx::play_cloak();
- }
- let atom = &mut redraw.make_guard(gfxtag!("netstatus overlay toggle"));
- overlay.set_property_bool(atom, Role::App, "is_visible", !is_visible).unwrap();
- /*
- // Show netstat-klik icon
- let netstat_klik =
- sg_root.lookup_node("/window/content/chat/netstatus_layer/netstat_klik").unwrap();
- {
- let atom = &mut redraw.make_guard(gfxtag!("netstat_klik_show"));
- if let Err(e) = netstat_klik.set_property_bool(atom, Role::App, "is_visible", true)
- {
- e!("Failed to show netstat_klik: {e}");
- }
- }
- // Trigger reconnect
- match sg_root.lookup_node("/plugin/darkirc") {
- Some(darkirc) => {
- if let Err(e) = darkirc.call_method("reconnect", vec![]).await {
- e!("Failed to trigger reconnect: {e}");
- }
- }
- None => {
- e!("DarkIrc plugin has not been loaded");
- }
- }
- msleep(200).await;
- // Hide netstat-klik icon
- {
- let atom = &mut redraw.make_guard(gfxtag!("netstat_klik_hide"));
- if let Err(e) = netstat_klik.set_property_bool(atom, Role::App, "is_visible", false)
- {
- e!("Failed to hide netstat_klik: {e}");
- }
- }
- */
- }
- });
- app.tasks.lock().unwrap().push(reconnect_task);
- let node =
- node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
- netlayer_node.link(node);
- // Overlay layer toggled by the netstatus logo. Sits on top of everything
- // except the header strip, so the logo stays visible and clickable.
- let overlay_node = create_layer("netstatus_overlay");
- let prop = overlay_node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, NETSTAT_OVERLAY_MARGIN).unwrap();
- prop.set_f32(atom, Role::App, 1, NETSTATUS_ICON_SIZE + NETSTAT_OVERLAY_MARGIN).unwrap();
- let code = cc.compile("w - 2 * NETSTAT_OVERLAY_MARGIN").unwrap();
- prop.set_expr(atom, Role::App, 2, code).unwrap();
- let code = cc.compile("h - NETSTATUS_ICON_SIZE - 2 * NETSTAT_OVERLAY_MARGIN").unwrap();
- prop.set_expr(atom, Role::App, 3, code).unwrap();
- overlay_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
- overlay_node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
- let overlay_node = overlay_node
- .setup(|me| Layer::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
- .await;
- chat_layer.link(overlay_node.clone());
- // Placeholder single-color background filling the whole overlay
- let node = create_vector_art("overlay_bg");
- let prop = node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, 0.).unwrap();
- prop.set_f32(atom, Role::App, 1, 0.).unwrap();
- prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
- prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
- node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
- node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
- let overlay_color = [0.05, 0.25, 0.3, 0.8];
- let mut shape = VectorShape::new();
- shape.add_filled_box(
- expr::const_f32(0.),
- expr::const_f32(0.),
- expr::load_var("w"),
- expr::load_var("h"),
- overlay_color,
- );
- let node = node
- .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
- .await;
- overlay_node.link(node);
- let node = create_text("info");
- prop.set_f32(atom, Role::App, 0, 0.).unwrap();
- prop.set_f32(atom, Role::App, 1, 0.).unwrap();
- prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
- prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
- node.set_property_f32(atom, Role::App, "font_size", NETSTAT_OVERLAY_BTN_FONTSIZE).unwrap();
- /*
- #[cfg(target_os = "android")]
- {
- let info = crate::android::get_display_debug_info();
- i!("Display debug report:\n{info}");
- node.set_property_str(atom, Role::App, "text", info).unwrap();
- }
- #[cfg(not(target_os = "android"))]
- {
- let info = indoc! {"
- nothing to see here
- folx
- hello
- "};
- node.set_property_str(atom, Role::App, "text", info).unwrap();
- }
- */
- node.set_property_enum(atom, Role::App, "text_align", "center").unwrap();
- let prop = node.get_property("text_color").unwrap();
- prop.set_f32(atom, Role::App, 0, 1.).unwrap();
- prop.set_f32(atom, Role::App, 1, 1.).unwrap();
- prop.set_f32(atom, Role::App, 2, 1.).unwrap();
- prop.set_f32(atom, Role::App, 3, 1.).unwrap();
- node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
- let node = node
- .setup(|me| {
- Text::new(
- me,
- window_scale.clone(),
- app.renderer.clone(),
- i18n_fish.clone(),
- app.redraw_trigger.clone(),
- )
- })
- .await;
- overlay_node.link(node);
- // Stop/start p2p button
- let node = create_button("copy_btn");
- node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
- let prop = node.get_property("rect").unwrap();
- prop.set_f32(atom, Role::App, 0, 0.).unwrap();
- prop.set_f32(atom, Role::App, 1, 0.).unwrap();
- prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
- prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
- let sg_root = app.sg_root.clone();
- let redraw = app.redraw_trigger.clone();
- let (slot, recvr) = Slot::new("stop_btn_clicked");
- node.register("click", slot).unwrap();
- let stop_task = app.ex.spawn(async move {
- let mut p2p_running = true;
- while let Ok(_) = recvr.recv().await {
- /*
- #[cfg(target_os = "android")]
- {
- let info = crate::android::get_display_debug_info();
- crate::clipboard::set(&info);
- i!("Copied report!");
- }
- */
- }
- });
- app.tasks.lock().unwrap().push(stop_task);
- let node =
- node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
- overlay_node.link(node);
- menu::make(
- app,
- chat_layer.clone(),
- i18n_fish,
- channels_tree.clone(),
- contacts_tree.clone(),
- &db,
- emoji_meshes.clone(),
- )
- .await;
- // Create chat layers only for joined channels/contacts, in joined order.
- for name in read_joined_channels() {
- let bare = if name.starts_with('#') || name.starts_with('@') { &name[1..] } else { &name };
- let in_tree = match name.chars().next() {
- Some('#') => channels_tree.contains_key(bare.to_string().as_bytes()).unwrap_or(false),
- Some('@') => contacts_tree.contains_key(bare.to_string().as_bytes()).unwrap_or(false),
- _ => false,
- };
- if !in_tree {
- warn!(target: "app::schema", "Joined entry '{name}' not found in tree; skipping");
- continue
- }
- chat::make(
- &app.sg_root,
- &app.renderer,
- &app.ex,
- chat_layer.clone(),
- &name,
- &db,
- i18n_fish,
- emoji_meshes.clone(),
- app.redraw_trigger.clone(),
- )
- .await;
- }
- wallet::make(app, content.clone(), i18n_fish).await;
- // Setup wallet button after wallet layer is created
- menu::setup_wallet_button(app, chat_layer, i18n_fish).await;
- // @@@ Debug stuff @@@
- //let chatview_node = app.sg_root.lookup_node("/window/content/chat/dev_chat_layer").unwrap();
- //chatview_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
- //let menu_node = app.sg_root.lookup_node("/window/content/chat/menu_layer").unwrap();
- //menu_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
- }
|