details:   https://code.tryton.org/tryton/commit/b4deb9f4d972
branch:    default
user:      Cédric Krier <[email protected]>
date:      Tue Dec 02 13:08:05 2025 +0100
description:
        Allow users to subscribe to chats
diffstat:

 sao/CHANGELOG                                                 |   1 +
 sao/images/tryton-notification-off.svg                        |   1 +
 sao/images/tryton-notification-on.svg                         |   1 +
 sao/src/chat.js                                               |  53 +++++++++++
 sao/src/common.js                                             |   2 +
 tryton/CHANGELOG                                              |   1 +
 tryton/tryton/chat.py                                         |  44 +++++++++
 tryton/tryton/data/pixmaps/tryton/tryton-notification-off.svg |   1 +
 tryton/tryton/data/pixmaps/tryton/tryton-notification-on.svg  |   1 +
 tryton/tryton/rpc.py                                          |  11 +-
 trytond/trytond/ir/chat.py                                    |  36 ++++++-
 trytond/trytond/ir/ui/menu.py                                 |   2 +
 12 files changed, 145 insertions(+), 9 deletions(-)

diffs (315 lines):

diff -r 481aed0501da -r b4deb9f4d972 sao/CHANGELOG
--- a/sao/CHANGELOG     Tue Dec 02 18:11:10 2025 +0100
+++ b/sao/CHANGELOG     Tue Dec 02 13:08:05 2025 +0100
@@ -1,3 +1,4 @@
+* Allow users to subscribe to chats
 * Manage user notification
 * Add search on empty relation field to domain parser
 * Escape completion content with custom format (issue14363)
diff -r 481aed0501da -r b4deb9f4d972 sao/images/tryton-notification-off.svg
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/sao/images/tryton-notification-off.svg    Tue Dec 02 13:08:05 2025 +0100
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; height="24px" viewBox="0 -960 960 960" 
width="24px" fill="#e3e3e3"><path d="M160-200v-80h80v-280q0-33 
8.5-65t25.5-61l60 60q-7 16-10.5 32.5T320-560v280h248L56-792l56-56 736 736-56 
56-146-144H160Zm560-154-80-80v-126q0-66-47-113t-113-47q-26 0-50 8t-44 
24l-58-58q20-16 43-28t49-18v-28q0-25 17.5-42.5T480-880q25 0 42.5 
17.5T540-820v28q80 20 130 84.5T720-560v206Zm-276-50Zm36 324q-33 
0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80Zm33-481Z"/></svg>
\ No newline at end of file
diff -r 481aed0501da -r b4deb9f4d972 sao/images/tryton-notification-on.svg
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/sao/images/tryton-notification-on.svg     Tue Dec 02 13:08:05 2025 +0100
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; height="24px" viewBox="0 -960 960 960" 
width="24px" fill="#e3e3e3"><path d="M80-560q0-100 44.5-183.5T244-882l47 64q-60 
44-95.5 111T160-560H80Zm720 0q0-80-35.5-147T669-818l47-64q75 55 119.5 
138.5T880-560h-80ZM160-200v-80h80v-280q0-83 50-147.5T420-792v-28q0-25 
17.5-42.5T480-880q25 0 42.5 17.5T540-820v28q80 20 130 
84.5T720-560v280h80v80H160Zm320-300Zm0 420q-33 0-56.5-23.5T400-160h160q0 
33-23.5 56.5T480-80ZM320-280h320v-280q0-66-47-113t-113-47q-66 0-113 47t-47 
113v280Z"/></svg>
\ No newline at end of file
diff -r 481aed0501da -r b4deb9f4d972 sao/src/chat.js
--- a/sao/src/chat.js   Tue Dec 02 18:11:10 2025 +0100
+++ b/sao/src/chat.js   Tue Dec 02 13:08:05 2025 +0100
@@ -50,6 +50,59 @@
                 'class': 'chat',
             });
 
+            let btn_group = jQuery('<div/>', {
+                'class': 'btn-group',
+                'role': 'group',
+            }).appendTo(el);
+
+            let subscribe_btn = jQuery('<button/>', {
+                'class': 'btn btn-default pull-right',
+                'type': 'button',
+                'title': Sao.i18n.gettext("Toggle notification"),
+            }).append(Sao.common.ICONFACTORY.get_icon_img(
+                'tryton-notification'))
+                .appendTo(btn_group);
+
+            Sao.rpc({
+                'method': 'model.ir.chat.channel.get_followers',
+                'params': [this.record, {}],
+            }, Sao.Session.current_session).then((followers) => {
+                set_subscribe_state(~followers.users.indexOf(
+                    Sao.Session.current_session.login));
+            })
+
+            let set_subscribe_state = (subscribed) => {
+                let img;
+                if (subscribed) {
+                    img = 'tryton-notification-on';
+                    subscribe_btn.addClass('active').off().click(unsubscribe);
+                } else {
+                    img = 'tryton-notification-off';
+                    subscribe_btn.removeClass('active').off().click(subscribe);
+                }
+                subscribe_btn.html(Sao.common.ICONFACTORY.get_icon_img(img));
+            }
+
+            let subscribe = () => {
+                let session = Sao.Session.current_session;
+                Sao.rpc({
+                    'method': 'model.ir.chat.channel.subscribe',
+                    'params': [this.record, {}],
+                }, session).then(() => {
+                    set_subscribe_state(true);
+                })
+            };
+
+            let unsubscribe = () => {
+                let session = Sao.Session.current_session;
+                Sao.rpc({
+                    'method': 'model.ir.chat.channel.unsubscribe',
+                    'params': [this.record, {}],
+                }, session).then(() => {
+                    set_subscribe_state(false);
+                })
+            };
+
             this._messages = jQuery('<div/>', {
                 'class': 'chat-messages',
             }).appendTo(jQuery('<div/>', {
diff -r 481aed0501da -r b4deb9f4d972 sao/src/common.js
--- a/sao/src/common.js Tue Dec 02 18:11:10 2025 +0100
+++ b/sao/src/common.js Tue Dec 02 13:08:05 2025 +0100
@@ -3155,6 +3155,8 @@
         'tryton-menu',
         'tryton-note',
         'tryton-notification',
+        'tryton-notification-off',
+        'tryton-notification-on',
         'tryton-ok',
         'tryton-open',
         'tryton-print',
diff -r 481aed0501da -r b4deb9f4d972 tryton/CHANGELOG
--- a/tryton/CHANGELOG  Tue Dec 02 18:11:10 2025 +0100
+++ b/tryton/CHANGELOG  Tue Dec 02 13:08:05 2025 +0100
@@ -1,3 +1,4 @@
+* Allow users to subscribe to chats
 * Manage user notification
 * Add search on empty relation field to domain parser
 * Add support for multiple button in the tree view
diff -r 481aed0501da -r b4deb9f4d972 tryton/tryton/chat.py
--- a/tryton/tryton/chat.py     Tue Dec 02 18:11:10 2025 +0100
+++ b/tryton/tryton/chat.py     Tue Dec 02 13:08:05 2025 +0100
@@ -84,9 +84,53 @@
             'size-allocate', scroll_to_bottom)
 
     def __build(self):
+        tooltips = common.Tooltips()
+
         widget = Gtk.VBox()
         widget.set_spacing(3)
 
+        hbuttonbox = Gtk.HButtonBox()
+        hbuttonbox.set_layout(Gtk.ButtonBoxStyle.END)
+        widget.pack_start(hbuttonbox, expand=False, fill=True, padding=0)
+
+        subscribe_btn = Gtk.ToggleButton()
+        subscribe_btn.set_image(common.IconFactory.get_image(
+                'tryton-notification', Gtk.IconSize.SMALL_TOOLBAR))
+        tooltips.set_tip(subscribe_btn, _("Toggle notification"))
+        subscribe_btn.set_relief(Gtk.ReliefStyle.NONE)
+        hbuttonbox.pack_start(
+            subscribe_btn, expand=False, fill=True, padding=0)
+        hbuttonbox.set_child_non_homogeneous(subscribe_btn, True)
+
+        followers = rpc.execute(
+            'model', 'ir.chat.channel', 'get_followers', self.record,
+            rpc.CONTEXT)
+
+        def set_subscribe_state(subscribed):
+            if subscribed:
+                img = 'tryton-notification-on'
+            else:
+                img = 'tryton-notification-off'
+            subscribe_btn.set_image(common.IconFactory.get_image(
+                    img, Gtk.IconSize.SMALL_TOOLBAR))
+
+        subscribed = rpc._LOGIN in followers['users']
+        set_subscribe_state(subscribed)
+        subscribe_btn.set_active(subscribed)
+
+        def toggle_subscribe(button):
+            if button.props.active:
+                rpc.execute(
+                    'model', 'ir.chat.channel', 'subscribe', self.record,
+                    rpc.CONTEXT)
+            else:
+                rpc.execute(
+                    'model', 'ir.chat.channel', 'unsubscribe', self.record,
+                    rpc.CONTEXT)
+            set_subscribe_state(button.props.active)
+
+        subscribe_btn.connect('toggled', toggle_subscribe)
+
         def _submit(button):
             buffer = input_.get_buffer()
             self.send_message(
diff -r 481aed0501da -r b4deb9f4d972 
tryton/tryton/data/pixmaps/tryton/tryton-notification-off.svg
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tryton/tryton/data/pixmaps/tryton/tryton-notification-off.svg     Tue Dec 
02 13:08:05 2025 +0100
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; height="24px" viewBox="0 -960 960 960" 
width="24px" fill="#e3e3e3"><path d="M160-200v-80h80v-280q0-33 
8.5-65t25.5-61l60 60q-7 16-10.5 32.5T320-560v280h248L56-792l56-56 736 736-56 
56-146-144H160Zm560-154-80-80v-126q0-66-47-113t-113-47q-26 0-50 8t-44 
24l-58-58q20-16 43-28t49-18v-28q0-25 17.5-42.5T480-880q25 0 42.5 
17.5T540-820v28q80 20 130 84.5T720-560v206Zm-276-50Zm36 324q-33 
0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80Zm33-481Z"/></svg>
\ No newline at end of file
diff -r 481aed0501da -r b4deb9f4d972 
tryton/tryton/data/pixmaps/tryton/tryton-notification-on.svg
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tryton/tryton/data/pixmaps/tryton/tryton-notification-on.svg      Tue Dec 
02 13:08:05 2025 +0100
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; height="24px" viewBox="0 -960 960 960" 
width="24px" fill="#e3e3e3"><path d="M80-560q0-100 44.5-183.5T244-882l47 64q-60 
44-95.5 111T160-560H80Zm720 0q0-80-35.5-147T669-818l47-64q75 55 119.5 
138.5T880-560h-80ZM160-200v-80h80v-280q0-83 50-147.5T420-792v-28q0-25 
17.5-42.5T480-880q25 0 42.5 17.5T540-820v28q80 20 130 
84.5T720-560v280h80v80H160Zm320-300Zm0 420q-33 0-56.5-23.5T400-160h160q0 
33-23.5 56.5T480-80ZM320-280h320v-280q0-66-47-113t-113-47q-66 0-113 47t-47 
113v280Z"/></svg>
\ No newline at end of file
diff -r 481aed0501da -r b4deb9f4d972 tryton/tryton/rpc.py
--- a/tryton/tryton/rpc.py      Tue Dec 02 18:11:10 2025 +0100
+++ b/tryton/tryton/rpc.py      Tue Dec 02 13:08:05 2025 +0100
@@ -20,6 +20,7 @@
 logger = logging.getLogger(__name__)
 CONNECTION = None
 _USER = None
+_LOGIN = None
 CONTEXT = {}
 _VIEW_CACHE = {}
 _TOOLBAR_CACHE = {}
@@ -86,7 +87,7 @@
 def set_service_session(parameters):
     from tryton import common
     from tryton.bus import Bus
-    global CONNECTION, _USER
+    global CONNECTION, _USER, _LOGIN
     host = CONFIG['login.host']
     hostname = common.get_hostname(host)
     port = common.get_port(host)
@@ -102,6 +103,7 @@
         if _USER != renew_id:
             raise ValueError
     _USER = user_id
+    _LOGIN = username
     bus_url_host = parameters.get('bus_url_host', [''])[0]
     session = ':'.join(map(str, [username, user_id, session]))
     if CONNECTION is not None:
@@ -114,7 +116,7 @@
 def login(parameters):
     from tryton import common
     from tryton.bus import Bus
-    global CONNECTION, _USER
+    global CONNECTION, _USER, _LOGIN
     host = CONFIG['login.host']
     hostname = common.get_hostname(host)
     port = common.get_port(host)
@@ -127,6 +129,7 @@
     result = connection.common.db.login(username, parameters, language)
     logger.debug('%r', result)
     _USER = result[0]
+    _LOGIN = username
     session = ':'.join(map(str, [username] + result[:2]))
     bus_url_host = result[2]
     if CONNECTION is not None:
@@ -138,7 +141,7 @@
 
 
 def logout():
-    global CONNECTION, _USER
+    global CONNECTION, _USER, _LOGIN
     if CONNECTION is not None:
         try:
             logger.info('common.db.logout()')
@@ -148,7 +151,7 @@
             pass
         CONNECTION.close()
         CONNECTION = None
-    _USER = None
+    _USER = _LOGIN = None
 
 
 def reset_password():
diff -r 481aed0501da -r b4deb9f4d972 trytond/trytond/ir/chat.py
--- a/trytond/trytond/ir/chat.py        Tue Dec 02 18:11:10 2025 +0100
+++ b/trytond/trytond/ir/chat.py        Tue Dec 02 13:08:05 2025 +0100
@@ -44,6 +44,7 @@
             unsubscribe=RPC(readonly=False),
             subscribe_email=RPC(readonly=False),
             unsubscribe_email=RPC(readonly=False),
+            get_followers=RPC(),
             post=RPC(readonly=False, result=int),
             get_models=RPC(),
             get=RPC(),
@@ -73,19 +74,24 @@
                 ])
         if channels:
             channel, = channels
-        else:
+        elif not Transaction().readonly:
             channel = cls(resource=str(resource))
             channel.save()
+        else:
+            return
         return channel
 
     @classmethod
-    def subscribe(cls, resource, username):
+    def subscribe(cls, resource, username=None):
         pool = Pool()
         Follower = pool.get('ir.chat.follower')
         User = pool.get('res.user')
-        user, = User.search([
-                ('login', '=', username),
-                ])
+        if username is not None:
+            user, = User.search([
+                    ('login', '=', username),
+                    ])
+        else:
+            user = User(Transaction().user)
         channel = cls._get_channel(resource)
         Follower.add_user(channel, user)
 
@@ -113,6 +119,26 @@
         Follower.remove_email(channel, email)
 
     @classmethod
+    def get_followers(cls, resource):
+        pool = Pool()
+        Follower = pool.get('ir.chat.follower')
+        users, emails = [], []
+        channel = cls._get_channel(resource)
+        if channel:
+            followers = Follower.search([
+                    ('channel', '=', channel),
+                    ])
+            for follower in followers:
+                if follower.user:
+                    users.append(follower.user.login)
+                elif follower.email:
+                    emails.append(follower.email)
+        return {
+            'users': users,
+            'emails': emails,
+            }
+
+    @classmethod
     def post(cls, resource, content, audience='internal'):
         pool = Pool()
         Message = pool.get('ir.chat.message')
diff -r 481aed0501da -r b4deb9f4d972 trytond/trytond/ir/ui/menu.py
--- a/trytond/trytond/ir/ui/menu.py     Tue Dec 02 18:11:10 2025 +0100
+++ b/trytond/trytond/ir/ui/menu.py     Tue Dec 02 13:08:05 2025 +0100
@@ -61,6 +61,8 @@
         'tryton-menu',
         'tryton-note',
         'tryton-notification',
+        'tryton-notification-off',
+        'tryton-notification-on',
         'tryton-ok',
         'tryton-open',
         'tryton-print',

Reply via email to