Browse Source

app: selecting custom channels to add them to main chat menu

darkfi 3 weeks ago
parent
commit
bac4f7917e

+ 62 - 67
bin/app/src/app/schema/chat.rs

@@ -33,7 +33,7 @@ use crate::{
         App,
     },
     expr::{self, Compiler},
-    gfx::gfxtag,
+    gfx::{gfxtag, Renderer},
     prop::{
         Property, PropertyAtomicGuard, PropertyBool, PropertyFloat32, PropertyStr, PropertySubType,
         PropertyType, Role,
@@ -45,6 +45,7 @@ use crate::{
         Shortcut, Text, VectorArt, VectorShape,
     },
     util::{i18n::I18nBabelFish, unixtime},
+    ExecutorPtr,
 };
 
 use super::{ColorScheme, COLOR_SCHEME};
@@ -181,21 +182,19 @@ fn is_ime_visible() -> bool {
 }
 
 pub async fn make(
-    app: &App,
+    sg_root: &SceneNodePtr,
+    renderer: &Renderer,
+    ex: &ExecutorPtr,
     content: SceneNodePtr,
     channel: &str,
     db: &sled::Db,
     i18n_fish: &I18nBabelFish,
     emoji_meshes: emoji_picker::EmojiMeshesPtr,
     is_first_time: bool,
-) {
-    let window_scale = PropertyFloat32::wrap(
-        &app.sg_root.lookup_node("/window").unwrap(),
-        Role::Internal,
-        "scale",
-        0,
-    )
-    .unwrap();
+) -> SceneNodePtr {
+    let window_scale =
+        PropertyFloat32::wrap(&sg_root.lookup_node("/window").unwrap(), Role::Internal, "scale", 0)
+            .unwrap();
     let atom = &mut PropertyAtomicGuard::none();
 
     let mut cc = Compiler::new();
@@ -222,7 +221,7 @@ pub async fn make(
     prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
     layer_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
     layer_node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
-    let layer_node = layer_node.setup(|me| Layer::new(me, app.renderer.clone())).await;
+    let layer_node = layer_node.setup(|me| Layer::new(me, renderer.clone())).await;
     content.link(layer_node.clone());
 
     // Create a bg mesh on top to fade the bg image
@@ -241,7 +240,7 @@ pub async fn make(
         expr::load_var("h"),
         [[0., 0., 0., 0.5], [0., 0., 0., 0.5], [0., 0., 0., 0.5], [0., 0., 0., 0.8]],
     );
-    let node = node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+    let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone())).await;
     layer_node.link(node);
 
     // Create the toolbar bg
@@ -292,7 +291,7 @@ pub async fn make(
         0.2,
     );
 
-    let node = node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+    let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone())).await;
     layer_node.link(node);
 
     // Create the send button
@@ -305,7 +304,7 @@ pub async fn make(
     node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
 
     let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
-    let node = node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+    let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone())).await;
     layer_node.link(node);
 
     // Create the back button
@@ -319,18 +318,18 @@ pub async fn make(
 
     // Menu doesn't exist yet ;)
     // So look it up in the callback.
-    let sg_root = app.sg_root.clone();
+    let sg_root2 = sg_root.clone();
     let layer_node2 = layer_node.clone();
     let chatview_is_visible = PropertyBool::wrap(&layer_node, Role::App, "is_visible", 0).unwrap();
-    let renderer = app.renderer.clone();
+    let renderer2 = renderer.clone();
     let goback = async move || {
         info!(target: "app::chat", "clicked back");
-        let atom = &mut renderer.make_guard(gfxtag!("goback action"));
+        let atom = &mut renderer2.make_guard(gfxtag!("goback action"));
 
         let editz_node = layer_node2.lookup_node("/content/editz").unwrap();
         editz_node.call_method("unfocus", vec![]).await.unwrap();
 
-        let menu_node = sg_root.lookup_node("/window/content/menu_layer").unwrap();
+        let menu_node = sg_root2.lookup_node("/window/content/menu_layer").unwrap();
         menu_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
 
         chatview_is_visible.set(atom, false);
@@ -339,14 +338,14 @@ pub async fn make(
     let (slot, recvr) = Slot::new("back_clicked");
     node.register("click", slot).unwrap();
     let goback2 = goback.clone();
-    let listen_click = app.ex.spawn(async move {
+    let listen_click = ex.spawn(async move {
         while let Ok(_) = recvr.recv().await {
             goback2().await;
         }
     });
     layer_node.push_task(listen_click);
 
-    let node = node.setup(|me| Button::new(me, app.renderer.clone())).await;
+    let node = node.setup(|me| Button::new(me, renderer.clone())).await;
     layer_node.link(node);
 
     // Create shortcut to go back as well
@@ -362,7 +361,7 @@ pub async fn make(
 
     let (slot, recvr) = Slot::new("back_pressed");
     node.register("shortcut", slot).unwrap();
-    let listen_enter = app.ex.spawn(async move {
+    let listen_enter = ex.spawn(async move {
         while let Ok(_) = recvr.recv().await {
             goback().await;
         }
@@ -398,7 +397,7 @@ pub async fn make(
     node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
 
     let node = node
-        .setup(|me| Text::new(me, window_scale.clone(), app.renderer.clone(), i18n_fish.clone()))
+        .setup(|me| Text::new(me, window_scale.clone(), renderer.clone(), i18n_fish.clone()))
         .await;
     layer_node.link(node);
 
@@ -419,7 +418,7 @@ pub async fn make(
     //node.set_property_f32(atom, Role::App, "font_size", FONTSIZE).unwrap();
     node.set_property_f32(atom, Role::App, "emoji_size", EMOJI_PICKER_ICON_SIZE).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
-    let node = node.setup(|me| EmojiPicker::new(me, app.renderer.clone(), emoji_meshes)).await;
+    let node = node.setup(|me| EmojiPicker::new(me, renderer.clone(), emoji_meshes)).await;
     let emoji_picker_node = node.clone();
     layer_node.link(node);
 
@@ -459,7 +458,7 @@ pub async fn make(
     //    expr::load_var("h"),
     //    [0.41, 0.6, 0.65, 1.],
     //);
-    let node = node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+    let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone())).await;
     layer_node.link(node);
 
     // Main content view
@@ -475,7 +474,7 @@ pub async fn make(
     layer_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
     layer_node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
     layer_node.set_property_u32(atom, Role::App, "priority", 1).unwrap();
-    let layer_node = layer_node.setup(|me| Layer::new(me, app.renderer.clone())).await;
+    let layer_node = layer_node.setup(|me| Layer::new(me, renderer.clone())).await;
     chat_layer_node.link(layer_node.clone());
 
     // ChatView
@@ -561,21 +560,15 @@ pub async fn make(
     debug!(target: "app", "Loaded #{channel} history: {} lines", chat_tree.len());
     let chatview_node = node
         .setup(|me| {
-            ChatView::new(
-                me,
-                chat_tree,
-                window_scale.clone(),
-                app.renderer.clone(),
-                app.sg_root.clone(),
-            )
+            ChatView::new(me, chat_tree, window_scale.clone(), renderer.clone(), sg_root.clone())
         })
         .await;
     layer_node.link(chatview_node.clone());
 
     let (slot, recvr) = Slot::new("fileurl_detect");
     chatview_node.register("fileurl_detected", slot).unwrap();
-    let sg_root2 = app.sg_root.clone();
-    let listen_fileurl = app.ex.spawn(async move {
+    let sg_root2 = sg_root.clone();
+    let listen_fileurl = ex.spawn(async move {
         while let Ok(data) = recvr.recv().await {
             if let Some(fud_node) = sg_root2.lookup_node("/plugin/fud") {
                 let _ = fud_node.call_method("track_file", data).await;
@@ -586,8 +579,8 @@ pub async fn make(
 
     let (slot, recvr) = Slot::new("file_download_request");
     chatview_node.register("file_download_request", slot).unwrap();
-    let sg_root2 = app.sg_root.clone();
-    let listen_file_download = app.ex.spawn(async move {
+    let sg_root2 = sg_root.clone();
+    let listen_file_download = ex.spawn(async move {
         while let Ok(data) = recvr.recv().await {
             if let Some(fud_node) = sg_root2.lookup_node("/plugin/fud") {
                 let _ = fud_node.call_method("get", data).await;
@@ -670,7 +663,7 @@ pub async fn make(
     //    expr::load_var("h"),
     //    [0.41, 0.6, 0.65, 1.],
     //);
-    let node = node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+    let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone())).await;
     layer_node.link(node);
 
     // Create the send button
@@ -684,7 +677,7 @@ pub async fn make(
     prop.set_f32(atom, Role::App, 3, 500.).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 5).unwrap();
     let shape = shape::create_send_arrow().scaled(EMOJI_SCALE);
-    let node = node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+    let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone())).await;
     layer_node.link(node);
 
     // Create the emoji button
@@ -702,7 +695,7 @@ pub async fn make(
         ColorScheme::PaperLight => [0., 0., 0., 1.],
     };
     let shape = shape::create_emoji_selector(color).scaled(EMOJI_SCALE);
-    let node = node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+    let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone())).await;
     layer_node.link(node);
 
     // Create the emoji button
@@ -717,7 +710,7 @@ pub async fn make(
     prop.set_f32(atom, Role::App, 3, 500.).unwrap();
     node.set_property_u32(atom, Role::App, "z_index", 5).unwrap();
     let shape = shape::create_close_icon().scaled(EMOJI_CLOSE_SCALE);
-    let node = node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+    let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone())).await;
     layer_node.link(node);
 
     // Text edit
@@ -816,7 +809,7 @@ pub async fn make(
 
     //let editbox_focus = PropertyBool::wrap(node, Role::App, "is_focused", 0).unwrap();
     //let darkirc_backend = app.darkirc_backend.clone();
-    //let task = app.ex.spawn(async move {
+    //let task = ex.spawn(async move {
     //    while let Ok(_) = btn_click_recvr.recv().await {
     //        let text = editbox_text.get();
     //        editbox_text.prop().unset(Role::App, 0).unwrap();
@@ -838,9 +831,9 @@ pub async fn make(
             BaseEdit::new(
                 me,
                 window_scale.clone(),
-                app.renderer.clone(),
+                renderer.clone(),
                 BaseEditType::MultiLine,
-                app.ex.clone(),
+                ex.clone(),
             )
         })
         .await;
@@ -850,7 +843,7 @@ pub async fn make(
     let (slot, recvr) = Slot::new("emoji_selected");
     emoji_picker_node.register("emoji_select", slot).unwrap();
     let chatedit_node2 = chatedit_node.clone();
-    let listen_click = app.ex.spawn(async move {
+    let listen_click = ex.spawn(async move {
         while let Ok(data) = recvr.recv().await {
             // No need to decode the data. Just pass it straight along
             chatedit_node2.call_method("insert_text", data).await.unwrap();
@@ -891,7 +884,7 @@ pub async fn make(
         ]
     );
     let node =
-        node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+        node.setup(|me| VectorArt::new(me, shape, renderer.clone())).await;
     layer_node.link(node);
     */
 
@@ -908,14 +901,14 @@ pub async fn make(
 
     let editz_text2 = editz_text.clone();
     let channel2 = channel.to_string();
-    let sg_root = app.sg_root.clone();
-    let renderer = app.renderer.clone();
+    let sg_root2 = sg_root.clone();
+    let renderer2 = renderer.clone();
     let sendmsg = move || {
         let editz_text = editz_text2.clone();
         let channel = channel2.clone();
-        let sg_root = sg_root.clone();
+        let sg_root = sg_root2.clone();
         let chatview_node = chatview_node.clone();
-        let renderer = renderer.clone();
+        let renderer = renderer2.clone();
         async move {
             let mut text = editz_text.get();
             info!(target: "app::chat", "Send '{text}' to channel: {channel}");
@@ -973,14 +966,14 @@ pub async fn make(
     let (slot, recvr) = Slot::new("send_clicked");
     node.register("click", slot).unwrap();
     let sendmsg2 = sendmsg.clone();
-    let listen_click = app.ex.spawn(async move {
+    let listen_click = ex.spawn(async move {
         while let Ok(_) = recvr.recv().await {
             sendmsg2().await;
         }
     });
     layer_node.push_task(listen_click);
 
-    let node = node.setup(|me| Button::new(me, app.renderer.clone())).await;
+    let node = node.setup(|me| Button::new(me, renderer.clone())).await;
     layer_node.link(node);
 
     // Create shortcut to send as well
@@ -989,7 +982,7 @@ pub async fn make(
 
     let (slot, recvr) = Slot::new("enter_pressed");
     node.register("shortcut", slot).unwrap();
-    let listen_enter = app.ex.spawn(async move {
+    let listen_enter = ex.spawn(async move {
         while let Ok(_) = recvr.recv().await {
             sendmsg().await;
         }
@@ -1014,7 +1007,7 @@ pub async fn make(
     chatedit_node.register("focus_request", slot).unwrap();
     let chatedit_node2 = chatedit_node.clone();
     let emoji_btn_is_visible2 = emoji_btn_is_visible.clone();
-    let listen_click = app.ex.spawn(async move {
+    let listen_click = ex.spawn(async move {
         while let Ok(_) = recvr.recv().await {
             if emoji_btn_is_visible2.get() {
                 debug!(target: "app::chat", "Emoji picker not visible so showing keyboard");
@@ -1028,8 +1021,8 @@ pub async fn make(
     let (slot, recvr) = Slot::new("emoji_clicked");
     let chatedit_node2 = chatedit_node.clone();
     node.register("click", slot).unwrap();
-    let renderer = app.renderer.clone();
-    let listen_click = app.ex.spawn(async move {
+    let renderer2 = renderer.clone();
+    let listen_click = ex.spawn(async move {
         let mut panel_height = if cfg!(target_os = "android") {
             let keyb_height = android_keyboard_height();
             if keyb_height > 0. {
@@ -1043,7 +1036,7 @@ pub async fn make(
 
         while let Ok(_) = recvr.recv().await {
             info!(target: "app::chat", "clicked emoji");
-            let atom = &mut renderer.make_guard(gfxtag!("emoji click action"));
+            let atom = &mut renderer2.make_guard(gfxtag!("emoji click action"));
 
             if cfg!(target_os = "android") {
                 let keyb_height = android_keyboard_height();
@@ -1084,7 +1077,7 @@ pub async fn make(
     });
     layer_node.push_task(listen_click);
 
-    let node = node.setup(|me| Button::new(me, app.renderer.clone())).await;
+    let node = node.setup(|me| Button::new(me, renderer.clone())).await;
     layer_node.link(node);
 
     // Commands help hint
@@ -1101,7 +1094,7 @@ pub async fn make(
     prop.add_depend(&editbox_bg_rect_prop, 1, "editz_bg_top_y");
     cmd_layer_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
     cmd_layer_node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
-    let cmd_layer_node = cmd_layer_node.setup(|me| Layer::new(me, app.renderer.clone())).await;
+    let cmd_layer_node = cmd_layer_node.setup(|me| Layer::new(me, renderer.clone())).await;
     layer_node.link(cmd_layer_node.clone());
 
     let cmd_hint_is_visible =
@@ -1119,11 +1112,11 @@ pub async fn make(
     let (slot, recvr) = Slot::new("nickcmd_clicked");
     node.register("click", slot).unwrap();
     let editz_text2 = editz_text.clone();
-    let renderer = app.renderer.clone();
-    let listen_click = app.ex.spawn(async move {
+    let renderer2 = renderer.clone();
+    let listen_click = ex.spawn(async move {
         while let Ok(_) = recvr.recv().await {
             info!(target: "app::chat", "clicked /nick");
-            let atom = &mut renderer.make_guard(gfxtag!("nickcmd_clicked action"));
+            let atom = &mut renderer2.make_guard(gfxtag!("nickcmd_clicked action"));
             // This will autohide this popup due to ending in a space.
             // Setting the property will retrigger the logic whether to show popup.
             editz_text2.set(atom, "/nick ");
@@ -1131,7 +1124,7 @@ pub async fn make(
     });
     layer_node.push_task(listen_click);
 
-    let node = node.setup(|me| Button::new(me, app.renderer.clone())).await;
+    let node = node.setup(|me| Button::new(me, renderer.clone())).await;
     cmd_layer_node.link(node);
 
     // Create the actionbar bg
@@ -1174,7 +1167,7 @@ pub async fn make(
         [0.29, 0.51, 0.45, 1.],
     );
 
-    let node = node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+    let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone())).await;
     cmd_layer_node.link(node);
 
     // Create some text
@@ -1196,7 +1189,7 @@ pub async fn make(
     node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
 
     let node = node
-        .setup(|me| Text::new(me, window_scale.clone(), app.renderer.clone(), i18n_fish.clone()))
+        .setup(|me| Text::new(me, window_scale.clone(), renderer.clone(), i18n_fish.clone()))
         .await;
     cmd_layer_node.link(node);
 
@@ -1219,13 +1212,13 @@ pub async fn make(
     node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
 
     let node = node
-        .setup(|me| Text::new(me, window_scale.clone(), app.renderer.clone(), i18n_fish.clone()))
+        .setup(|me| Text::new(me, window_scale.clone(), renderer.clone(), i18n_fish.clone()))
         .await;
     cmd_layer_node.link(node);
 
     let editz_text_sub = editz_text.prop().subscribe_modify();
-    let renderer = app.renderer.clone();
-    let editz_text_task = app.ex.spawn(async move {
+    let renderer = renderer.clone();
+    let editz_text_task = ex.spawn(async move {
         while let Ok(_) = editz_text_sub.receive().await {
             let atom = &mut renderer.make_guard(gfxtag!("chatedit txt changed"));
 
@@ -1247,6 +1240,8 @@ pub async fn make(
         }
     });
     layer_node.push_task(editz_text_task);
+
+    layer_node
 }
 
 // Just for testing

+ 67 - 4
bin/app/src/app/schema/menu/channel.rs

@@ -32,8 +32,8 @@ pub struct Channel {
 }
 
 use super::{
-    edit_buttons, edit_switch::edit_switch, ColorScheme, BTN_TEXT_Y, CHANNEL_ITEM_HEIGHT,
-    COLOR_SCHEME, MENU_BTN_W_L,
+    super::chat, edit_buttons, edit_switch::edit_switch, ColorScheme, BTN_TEXT_Y,
+    CHANNEL_ITEM_HEIGHT, COLOR_SCHEME, MENU_BTN_W_L,
 };
 use crate::{
     app::{
@@ -50,8 +50,8 @@ use crate::{
     scene::{SceneNodePtr, Slot},
     shape,
     ui::{
-        BaseEdit, BaseEditType, Button, Layer, Menu, ShapeVertex, Shortcut, Text, VectorArt,
-        VectorShape,
+        emoji_picker::EmojiMeshesPtr, BaseEdit, BaseEditType, Button, Layer, Menu, ShapeVertex,
+        Shortcut, Text, VectorArt, VectorShape,
     },
     util::i18n::I18nBabelFish,
 };
@@ -142,6 +142,9 @@ pub async fn make(
     contact_is_visible: PropertyBool,
     channel_is_visible: PropertyBool,
     channels_tree: sled::Tree,
+    db: &sled::Db,
+    emoji_meshes: EmojiMeshesPtr,
+    is_first_time: bool,
 ) -> SceneNodePtr {
     let mut cc = expr::Compiler::new();
     cc.add_const_f32("CHATEDIT_PAD", CHATEDIT_PAD);
@@ -1385,5 +1388,65 @@ pub async fn make(
         app.ex.clone(),
     );
 
+    // Register select signal on nick_menu
+    let (slot, recvr) = Slot::new("channel_selected");
+    menu_node.register("select", slot).unwrap();
+
+    let sg_root = app.sg_root.clone();
+    let renderer = app.renderer.clone();
+    let ex = app.ex.clone();
+    let channels_tree_clone = channels_tree.clone();
+    let content_clone = content.clone();
+    let channel_vis = channel_is_visible.clone();
+    let window_scale_clone = window_scale.clone();
+    let db_clone = db.clone();
+    let i18n_fish_clone = i18n_fish.clone();
+    let emoji_meshes_clone = emoji_meshes.clone();
+
+    let listen_select = app.ex.spawn(async move {
+        while let Ok(data) = recvr.recv().await {
+            let channel_name: String = deserialize(&data).unwrap();
+            let path = format!("/window/content/{}_chat_layer", &channel_name);
+
+            // Check if chat layer already exists
+            if sg_root.lookup_node(&path).is_some() {
+                continue;
+            }
+
+            // Create the chat layer and get the node
+            let node = chat::make(
+                &sg_root,
+                &renderer,
+                &ex,
+                content_clone.clone(),
+                &channel_name,
+                &db_clone,
+                &i18n_fish_clone,
+                emoji_meshes_clone.clone(),
+                is_first_time,
+            )
+            .await;
+
+            // Show the chat layer
+            let atom = &mut renderer.make_guard(gfxtag!("channel_selected"));
+            node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
+
+            // Add to main_menu items (check if not already there)
+            let main_menu = sg_root.lookup_node("/window/content/menu_layer/main_menu").unwrap();
+            let items_prop = main_menu.get_property("items").unwrap();
+
+            if !items_prop.contains_str(&channel_name) {
+                items_prop.push_str(atom, Role::App, &channel_name).unwrap();
+            }
+
+            // Hide channel screen
+            channel_vis.set(atom, false);
+
+            i!("Selected channel: {}", channel_name);
+        }
+    });
+
+    app.tasks.lock().unwrap().push(listen_select);
+
     content
 }

+ 19 - 12
bin/app/src/app/schema/menu/mod.rs

@@ -20,7 +20,7 @@ use darkfi_serial::deserialize;
 use sled_overlay::sled;
 use ui_consts::*;
 
-use super::{ColorScheme, CHANNELS, COLOR_SCHEME};
+use super::{ColorScheme, COLOR_SCHEME};
 use crate::{
     app::{
         node::{create_button, create_layer, create_menu, create_text, create_vector_art},
@@ -32,9 +32,13 @@ use crate::{
     prop::{PropertyAtomicGuard, PropertyBool, PropertyFloat32, Role},
     scene::{SceneNodePtr, Slot},
     shape,
-    ui::{Button, Layer, Menu, ShapeVertex, Text, VectorArt, VectorShape},
+    ui::{
+        emoji_picker::EmojiMeshesPtr, Button, Layer, Menu, ShapeVertex, Text, VectorArt,
+        VectorShape,
+    },
     util::i18n::I18nBabelFish,
 };
+use channel::Channel;
 
 #[cfg(any(target_os = "android", feature = "emulate-android"))]
 mod android_ui_consts {
@@ -96,7 +100,7 @@ mod ui_consts {
     pub const LABEL_LINESPACE: f32 = 140.;
 }
 
-mod channel;
+pub mod channel;
 mod contact;
 mod edit_buttons;
 mod edit_switch;
@@ -106,6 +110,9 @@ pub async fn make(
     content: SceneNodePtr,
     i18n_fish: &I18nBabelFish,
     channels_tree: sled::Tree,
+    db: &sled::Db,
+    emoji_meshes: EmojiMeshesPtr,
+    is_first_time: bool,
 ) {
     let window_scale = PropertyFloat32::wrap(
         &app.sg_root.lookup_node("/window").unwrap(),
@@ -190,7 +197,10 @@ pub async fn make(
         window_scale.clone(),
         contact_is_visible.clone(),
         channel_is_visible.clone(),
-        channels_tree,
+        channels_tree.clone(),
+        db,
+        emoji_meshes.clone(),
+        is_first_time,
     )
     .await;
 
@@ -434,14 +444,11 @@ pub async fn make(
     node.set_property_f32(atom, Role::App, "fade_zone", MENU_FADE).unwrap();
 
     let prop = node.get_property("items").unwrap();
-    for channel in CHANNELS {
-        prop.push_str(atom, Role::App, *channel).unwrap();
-    }
-    for channel in [
-        "@john", "@stacy", "@barry", "@steve", "@obombo", "@xyz", "@lunar", "@fren", "@anon",
-        "@anon1",
-    ] {
-        prop.push_str(atom, Role::App, channel).unwrap();
+    for item in channels_tree.iter() {
+        let (key, val) = item.unwrap();
+        let channel = deserialize::<Channel>(&val).unwrap();
+        let channel_name = format!("#{}", channel.name);
+        prop.push_str(atom, Role::App, &channel_name).unwrap();
     }
 
     let (slot, recvr) = Slot::new("menu_clicked");

+ 36 - 6
bin/app/src/app/schema/mod.rs

@@ -17,6 +17,7 @@
  */
 
 use darkfi::system::msleep;
+use darkfi_serial::{deserialize, Encodable};
 use indoc::indoc;
 use sled_overlay::sled;
 use std::fs::File;
@@ -37,6 +38,7 @@ use crate::{
 
 mod chat;
 mod menu;
+use menu::channel::Channel;
 //mod settings;
 pub mod test;
 pub mod test_scroll_layer;
@@ -117,8 +119,8 @@ mod ui_consts {
 
 pub use ui_consts::*;
 
-pub static CHANNELS: &'static [&str] =
-    &["#dev", "#media", "#hackers", "#memes", "#philosophy", "#markets", "#math", "#random"];
+pub static DEFAULT_CHANNELS: &'static [&str] =
+    &["dev", "media", "hackers", "memes", "philosophy", "markets", "math", "random"];
 
 #[derive(PartialEq)]
 enum ColorScheme {
@@ -591,11 +593,30 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
     let chatdb_path = get_chatdb_path();
     let db = sled::open(chatdb_path).expect("cannot open sleddb");
     let channels_tree = db.open_tree("channels").expect("cannot open channels tree");
-    for channel in CHANNELS {
+
+    // Initialize default channels if tree is empty
+    if channels_tree.is_empty() {
+        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, val).unwrap();
+        }
+        let _ = channels_tree.flush_async().await;
+    }
+
+    // Initialize chat layers from channels in database
+    for item in channels_tree.iter() {
+        let (key, val) = item.unwrap();
+        let channel = deserialize::<Channel>(&val).unwrap();
+        let channel_name = format!("#{}", channel.name);
+
         chat::make(
-            app,
+            &app.sg_root,
+            &app.renderer,
+            &app.ex,
             content.clone(),
-            channel,
+            &channel_name,
             &db,
             i18n_fish,
             emoji_meshes.clone(),
@@ -603,7 +624,16 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
         )
         .await;
     }
-    menu::make(app, content.clone(), i18n_fish, channels_tree).await;
+    menu::make(
+        app,
+        content.clone(),
+        i18n_fish,
+        channels_tree,
+        &db,
+        emoji_meshes.clone(),
+        is_first_time,
+    )
+    .await;
     wallet::make(app, content.clone(), i18n_fish).await;
 
     // Setup wallet button after wallet layer is created

+ 13 - 0
bin/app/src/prop/mod.rs

@@ -1166,6 +1166,19 @@ impl Property {
         })
     }
 
+    // Contains
+
+    pub fn contains_str(&self, s: &str) -> bool {
+        for i in 0..self.get_len() {
+            if let Ok(item) = self.get_str(i) {
+                if item == s {
+                    return true;
+                }
+            }
+        }
+        false
+    }
+
     // Subs
 
     pub fn subscribe_modify(&self) -> Subscription<(Role, ModifyAction, BatchGuardPtr)> {