[fractal] Add typing notifications



commit 48c8013be424db5178e68232e9dd68f7725b8d81
Author: Rasmus Rendal <rasmus rend al>
Date:   Thu Mar 7 08:40:03 2019 +0000

    Add typing notifications
    
    Add handler for incoming typing notifications in the sync loop
    Add an appop handler for typing notifications to the frontend
    
    Feature added for #305

 fractal-gtk/res/app.css                  |  7 +++++
 fractal-gtk/src/app/backend_loop.rs      |  2 +-
 fractal-gtk/src/appop/room.rs            | 46 ++++++++++++++++++++++++++++++--
 fractal-gtk/src/widgets/room_history.rs  |  4 +++
 fractal-gtk/src/widgets/scroll_widget.rs | 30 ++++++++++++++++++++-
 fractal-matrix-api/src/backend/sync.rs   | 37 ++++++++++++++++++++++++-
 fractal-matrix-api/src/backend/types.rs  |  2 +-
 fractal-matrix-api/src/model/room.rs     |  1 +
 8 files changed, 123 insertions(+), 6 deletions(-)
---
diff --git a/fractal-gtk/res/app.css b/fractal-gtk/res/app.css
index 02a8ec1c..b41e8bcf 100644
--- a/fractal-gtk/res/app.css
+++ b/fractal-gtk/res/app.css
@@ -310,3 +310,10 @@ stack.titlebar:not(headerbar) > box > separator {
 .scrollarea-top-border {
   border-top: 1px solid @borders;
 }
+
+.typing_label {
+  margin: 6px;
+  margin-left: 78px;
+  margin-bottom: 8px;
+  color: @theme_selected_bg_color;
+}
diff --git a/fractal-gtk/src/app/backend_loop.rs b/fractal-gtk/src/app/backend_loop.rs
index 999910e1..4b70e155 100644
--- a/fractal-gtk/src/app/backend_loop.rs
+++ b/fractal-gtk/src/app/backend_loop.rs
@@ -112,7 +112,7 @@ pub fn backend_loop(rx: Receiver<BKResponse>) {
                         APPOP!(set_active_room_by_id, (room_id));
                     }
                 }
-                Ok(BKResponse::NewRooms(rooms)) => {
+                Ok(BKResponse::UpdateRooms(rooms)) => {
                     let clear_room_list = false;
                     APPOP!(set_rooms, (rooms, clear_room_list));
                 }
diff --git a/fractal-gtk/src/appop/room.rs b/fractal-gtk/src/appop/room.rs
index f055f601..0004f63f 100644
--- a/fractal-gtk/src/appop/room.rs
+++ b/fractal-gtk/src/appop/room.rs
@@ -1,4 +1,4 @@
-use crate::i18n::{i18n, i18n_k};
+use crate::i18n::{i18n, i18n_k, ni18n_f};
 use log::{error, warn};
 use std::fs::remove_file;
 use std::os::unix::fs;
@@ -18,13 +18,15 @@ use crate::actions::AppState;
 use crate::cache;
 use crate::widgets;
 
-use crate::types::{Room, RoomMembership, RoomTag};
+use crate::types::{Member, Room, RoomMembership, RoomTag};
 
 use crate::util::markup_text;
 
 use rand::distributions::Alphanumeric;
 use rand::{thread_rng, Rng};
 
+use glib::functions::markup_escape_text;
+
 pub struct Force(pub bool);
 
 impl AppOp {
@@ -49,6 +51,14 @@ impl AppOp {
                 }
             } else if self.rooms.contains_key(&room.id) {
                 // TODO: update the existing rooms
+                let update_room = self.rooms.get_mut(&room.id).unwrap();
+                let typing_users: Vec<Member> = room
+                    .typing_users
+                    .iter()
+                    .map(|u| update_room.members.get(&u.uid).unwrap_or(&u).to_owned())
+                    .collect();
+                update_room.typing_users = typing_users;
+                self.update_typing_notification();
             } else {
                 // Request all joined members for each new room
                 self.backend
@@ -178,6 +188,7 @@ impl AppOp {
         self.active_room = Some(active_room);
         /* Mark the new active room as read */
         self.mark_last_message_as_read(Force(false));
+        self.update_typing_notification();
     }
 
     pub fn really_leave_active_room(&mut self) {
@@ -514,4 +525,35 @@ impl AppOp {
 
         self.backend.send(BKCommand::GetRoomAvatar(roomid)).unwrap();
     }
+
+    pub fn update_typing_notification(&mut self) {
+        if let Some(active_room) = &self
+            .rooms
+            .get(&self.active_room.clone().unwrap_or_default())
+        {
+            if let Some(ref mut history) = self.history {
+                let typing_users = &active_room.typing_users;
+                if typing_users.len() == 0 {
+                    history.typing_notification("");
+                } else if typing_users.len() > 2 {
+                    history.typing_notification(&i18n("Several users are typing…"));
+                } else {
+                    let typing_string = ni18n_f(
+                        "<b>{}</b> is typing…",
+                        "<b>{}</b> and {} are typing…",
+                        typing_users.len() as u32,
+                        typing_users
+                            .iter()
+                            .map(|user| markup_escape_text(&user.get_alias()))
+                            .collect::<Vec<String>>()
+                            .iter()
+                            .map(std::ops::Deref::deref)
+                            .collect::<Vec<&str>>()
+                            .as_slice(),
+                    );
+                    history.typing_notification(&typing_string);
+                }
+            }
+        }
+    }
 }
diff --git a/fractal-gtk/src/widgets/room_history.rs b/fractal-gtk/src/widgets/room_history.rs
index bc40e24f..9ab8a6c9 100644
--- a/fractal-gtk/src/widgets/room_history.rs
+++ b/fractal-gtk/src/widgets/room_history.rs
@@ -271,6 +271,10 @@ impl RoomHistory {
 
         None
     }
+
+    pub fn typing_notification(&mut self, typing_str: &str) {
+        self.rows.borrow().view.typing_notification(typing_str);
+    }
 }
 
 /* This function creates the content for a Row based on the conntent of msg */
diff --git a/fractal-gtk/src/widgets/scroll_widget.rs b/fractal-gtk/src/widgets/scroll_widget.rs
index 8fac41fe..8fd53639 100644
--- a/fractal-gtk/src/widgets/scroll_widget.rs
+++ b/fractal-gtk/src/widgets/scroll_widget.rs
@@ -35,6 +35,7 @@ pub struct Widgets {
     btn_revealer: gtk::Revealer,
     listbox: gtk::ListBox,
     spinner: gtk::Spinner,
+    typing_label: gtk::Label,
 }
 
 impl Widgets {
@@ -67,7 +68,24 @@ impl Widgets {
         let column = column.downcast::<gtk::Container>().unwrap();
         column.set_hexpand(true);
         column.set_vexpand(true);
-        column.add(&messages);
+
+        let typing_label = gtk::Label::new(None);
+        typing_label.show();
+        typing_label
+            .get_style_context()
+            .unwrap()
+            .add_class("typing_label");
+        typing_label.set_xalign(0.0);
+        typing_label.set_property_wrap(true);
+        typing_label.set_property_wrap_mode(pango::WrapMode::WordChar);
+        typing_label.set_visible(false);
+        typing_label.set_use_markup(true);
+
+        let column_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
+        column_box.add(&messages);
+        column_box.add(&typing_label);
+        column_box.show();
+        column.add(&column_box);
         column.show();
 
         messages
@@ -93,6 +111,7 @@ impl Widgets {
             btn_revealer,
             listbox: messages,
             spinner,
+            typing_label,
         }
     }
 }
@@ -246,6 +265,15 @@ impl ScrollWidget {
         self.request_sent.set(false);
         self.widgets.spinner.stop();
     }
+
+    pub fn typing_notification(&self, typing_str: &str) {
+        if typing_str.len() == 0 {
+            self.widgets.typing_label.set_visible(false);
+        } else {
+            self.widgets.typing_label.set_visible(true);
+            self.widgets.typing_label.set_markup(typing_str);
+        }
+    }
 }
 
 /* Functions to animate the scroll */
diff --git a/fractal-matrix-api/src/backend/sync.rs b/fractal-matrix-api/src/backend/sync.rs
index 23c8d914..f76ce26c 100644
--- a/fractal-matrix-api/src/backend/sync.rs
+++ b/fractal-matrix-api/src/backend/sync.rs
@@ -5,16 +5,21 @@ use crate::globals;
 use crate::types::Event;
 use crate::types::EventFilter;
 use crate::types::Filter;
+use crate::types::Member;
 use crate::types::Message;
 use crate::types::Room;
 use crate::types::RoomEventFilter;
 use crate::types::RoomFilter;
+use crate::types::RoomMembership;
+use crate::types::RoomTag;
 use crate::types::SyncResponse;
 use crate::types::UnreadNotificationsCount;
 use crate::util::json_q;
 use crate::util::parse_m_direct;
+
 use log::error;
 use serde_json::json;
+use serde_json::value::from_value;
 use serde_json::Value as JsonValue;
 use std::{thread, time};
 
@@ -92,7 +97,7 @@ pub fn sync(bk: &Backend, new_since: Option<String>, initial: bool) -> Result<()
 
                     // New rooms
                     let rs = Room::from_sync_response(&response, &userid, &baseu);
-                    tx.send(BKResponse::NewRooms(rs)).unwrap();
+                    tx.send(BKResponse::UpdateRooms(rs)).unwrap();
 
                     // Message events
                     let msgs = join
@@ -114,6 +119,36 @@ pub fn sync(bk: &Backend, new_since: Option<String>, initial: bool) -> Result<()
                             .unwrap();
                     }
 
+                    // Typing notifications
+                    let rooms: Vec<Room> = join
+                        .iter()
+                        .map(|(k, room)| {
+                            let ephemerals = &room.ephemeral.events;
+                            let mut typing_room: Room =
+                                Room::new(k.clone(), RoomMembership::Joined(RoomTag::None));
+                            let mut typing: Vec<Member> = Vec::new();
+                            for event in ephemerals.iter() {
+                                if let Some(typing_users) = event
+                                    .get("content")
+                                    .and_then(|x| x.get("user_ids"))
+                                    .and_then(|x| x.as_array())
+                                {
+                                    for user in typing_users {
+                                        let user: String = from_value(user.to_owned()).unwrap();
+                                        typing.push(Member {
+                                            uid: user,
+                                            alias: None,
+                                            avatar: None,
+                                        });
+                                    }
+                                }
+                            }
+                            typing_room.typing_users = typing;
+                            typing_room
+                        })
+                        .collect();
+                    tx.send(BKResponse::UpdateRooms(rooms)).unwrap();
+
                     // Other events
                     join.iter()
                         .flat_map(|(k, room)| {
diff --git a/fractal-matrix-api/src/backend/types.rs b/fractal-matrix-api/src/backend/types.rs
index 9f72a41d..f9cda612 100644
--- a/fractal-matrix-api/src/backend/types.rs
+++ b/fractal-matrix-api/src/backend/types.rs
@@ -105,7 +105,7 @@ pub enum BKResponse {
     SetUserAvatar(String),
     Sync(String),
     Rooms(Vec<Room>, Option<Room>),
-    NewRooms(Vec<Room>),
+    UpdateRooms(Vec<Room>),
     RoomDetail(String, String, String),
     RoomAvatar(String, Option<Url>),
     NewRoomAvatar(String),
diff --git a/fractal-matrix-api/src/model/room.rs b/fractal-matrix-api/src/model/room.rs
index b2ff1f1c..3f4fd955 100644
--- a/fractal-matrix-api/src/model/room.rs
+++ b/fractal-matrix-api/src/model/room.rs
@@ -81,6 +81,7 @@ pub struct Room {
     pub membership: RoomMembership,
     pub direct: bool,
     pub prev_batch: Option<String>,
+    pub typing_users: Vec<Member>,
 
     /// Hashmap with the room users power levels
     /// the key will be the userid and the value will be the level


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]