private_chat.py 10.8 KB
Newer Older
1
2
3
4
# Copyright (c) 2020 Nico Alt
# SPDX-License-Identifier: AGPL-3.0-only
# License-Filename: LICENSE.md

5
6
import time

Nico's avatar
Nico committed
7
from gettext import gettext as _
Nico's avatar
Nico committed
8
from gi.repository import Gdk, Gtk, GLib
Nico's avatar
Nico committed
9
10
11
12
13
14

from briar_wrapper.models.contacts import Contacts
from briar_wrapper.models.private_chat import PrivateChat

from briar_gtk.define import APP
from briar_gtk.widgets.edit_dialog import EditDialog
15
16


Nico's avatar
Nico committed
17
# pylint: disable=too-many-arguments
Nico's avatar
Nico committed
18
class PrivateChatPresenter:
Nico's avatar
Nico committed
19

20
21
    def __init__(self, private_chat_view, sidebar_presenter):
        self._signals = list()
Nico's avatar
Nico committed
22

23
24
25
        # TODO: Move whole sidebar presenter logic into briar_wrapper by
        # notifying sidebar about changes to private chat from model
        self._sidebar_presenter = sidebar_presenter
Nico's avatar
Nico committed
26

27
        self._view = private_chat_view
Nico's avatar
Nico committed
28

29
        self._open_private_chat()
Nico's avatar
Nico committed
30
31

    def open_change_contact_alias_dialog(self):
32
        if self._view.contact_id == 0:
Nico's avatar
Nico committed
33
34
35
36
37
            raise Exception("Can't change contact alias with ID 0")

        confirmation_dialog = EditDialog(
            parent=APP().window,
            flags=Gtk.DialogFlags.MODAL,
38
            placeholder=self._get_contact_name(self._view.contact_id)
Nico's avatar
Nico committed
39
40
41
42
43
44
45
46
        )

        confirmation_dialog.set_title(_("Change contact name"))

        response = confirmation_dialog.run()
        user_alias = confirmation_dialog.get_entry().get_text()
        confirmation_dialog.destroy()
        if (response == Gtk.ResponseType.OK) and (user_alias != ''):
47
48
            Contacts(APP().api).set_alias(self._view.contact_id, user_alias)
            self._set_contact_name_label(user_alias)
Nico's avatar
Nico committed
49
            self._sidebar_presenter.refresh_contacts()
50
            # TODO: Update name in chat history
Nico's avatar
Nico committed
51
52

    def open_delete_all_messages_dialog(self):
53
        if self._view.contact_id == 0:
Nico's avatar
Nico committed
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
            raise Exception("Can't delete all messages with contact ID 0")

        confirmation_dialog = Gtk.MessageDialog(
            transient_for=APP().window,
            flags=Gtk.DialogFlags.MODAL,
            message_type=Gtk.MessageType.WARNING,
            buttons=Gtk.ButtonsType.OK_CANCEL,
            text=_("Confirm Message Deletion"),
        )
        confirmation_dialog.format_secondary_text(
            _("Are you sure that you want to delete all messages?")
        )

        confirmation_dialog.connect("response", self._delete_all_messages)
        confirmation_dialog.show_all()

    def open_delete_contact_dialog(self):
71
        if self._view.contact_id == 0:
Nico's avatar
Nico committed
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
            raise Exception("Can't delete contact with ID 0")

        confirmation_dialog = Gtk.MessageDialog(
            transient_for=APP().window,
            flags=Gtk.DialogFlags.MODAL,
            message_type=Gtk.MessageType.WARNING,
            buttons=Gtk.ButtonsType.OK_CANCEL,
            text=_("Confirm Contact Deletion"),
        )
        confirmation_dialog.format_secondary_text(
            _("Are you sure that you want to remove this contact and "
              "all messages exchanged with this contact?")
        )

        confirmation_dialog.connect("response", self._delete_contact)
        confirmation_dialog.show_all()

Nico's avatar
Nico committed
89
    def open_emoji_menu(self):
Nico's avatar
Nico committed
90
91
        chat_input = self._view.builder.get_object("chat_input")
        chat_input.emit("insert-emoji")
Nico's avatar
Nico committed
92

93
94
95
96
97
98
99
100
101
102
103
    def close_private_chat(self):  # formerly `show_sidebar`
        main_content_stack = self._view.builder.get_object(
            "main_content_stack")
        self._hide_chat_view(main_content_stack)
        self._show_chat_placeholder(main_content_stack)
        self._show_sidebar_box()
        self._clear_history_container()
        self._unselect_contact()
        self._set_contact_name_label("")
        self._view.contact_id = 0
        self._hide_chat_menu_button()
Nico's avatar
Nico committed
104
        self._disconnect_chat_input_signals()
105
106
107
108
109
110
111

    def disconnect_signals(self):
        for signal in self._signals:
            APP().api.socket_listener.disconnect(signal)

    def _open_private_chat(self):
        contact_name = self._get_contact_name(self._view.contact_id)
Nico's avatar
Nico committed
112
        self._prepare_chat_view(contact_name)
113
        self._load_content(contact_name)
Nico's avatar
Nico committed
114

Nico's avatar
Nico committed
115
    @staticmethod
116
    def _get_contact_name(contact_id):  # TODO: Move into briar_wrapper
Nico's avatar
Nico committed
117
118
119
120
121
122
123
124
125
        name = ""
        for contact in Contacts(APP().api).get():
            if contact["contactId"] == contact_id:
                name = contact["author"]["name"]
                if "alias" in contact:
                    name = contact["alias"]
                break
        return name

126
127
128
    def _prepare_chat_view(self, contact_name):
        main_content_stack = self._view.builder.get_object(
            "main_content_stack")
Nico's avatar
Nico committed
129

130
131
132
133
134
        self._hide_chat_placeholder(main_content_stack)
        self._show_chat_view(main_content_stack)
        self._show_main_content_container()
        self._set_contact_name_label(contact_name)
        self._show_chat_menu_button()
Nico's avatar
Nico committed
135

136
137
    @staticmethod
    def _hide_chat_placeholder(main_content_stack):
Nico's avatar
Nico committed
138
139
        chat_placeholder = main_content_stack.get_child_by_name(
            "chat_placeholder")
140
        chat_placeholder.hide()
Nico's avatar
Nico committed
141

142
143
    @staticmethod
    def _show_chat_view(main_content_stack):
Nico's avatar
Nico committed
144
145
        chat_view = main_content_stack.get_child_by_name("chat_view")
        chat_view.show()
146
147
148
149
150

    def _show_main_content_container(self):
        main_window_leaflet = self._view.builder.get_object(
            "main_window_leaflet")
        main_content_container = self._view.builder.get_object(
Nico's avatar
Nico committed
151
152
            "main_content_container")
        main_window_leaflet.set_visible_child(main_content_container)
153
154
155

    def _set_contact_name_label(self, contact_name):
        contact_name_label = self._view.builder.get_object("contact_name")
Nico's avatar
Nico committed
156
157
        contact_name_label.set_text(contact_name)

158
159
160
    def _show_chat_menu_button(self):
        chat_menu_button = self._view.builder.get_object("chat_menu_button")
        chat_menu_button.show()
Nico's avatar
Nico committed
161

162
163
164
165
166
167
168
169
170
    def _load_content(self, contact_name):
        private_chat = PrivateChat(APP().api, self._view.contact_id)
        messages = private_chat.get()

        self._view.setup_view(contact_name)
        self._view.show_messages(messages)
        self._setup_message_listener()
        self._mark_messages_read(messages, private_chat)
        self._setup_history_container()
Nico's avatar
Nico committed
171
        self._setup_chat_input()
172
173
174
175
176
177
178

    def _setup_message_listener(self):
        # TODO: Move into briar_wrapper by adding function to PrivateChatModel
        socket_listener = APP().api.socket_listener
        signal_id = socket_listener.connect("ConversationMessageReceivedEvent",
                                            self._view.add_message_async)
        self._signals.append(signal_id)
Nico's avatar
Nico committed
179

180
181
182
183
184
185
186
187
188
    @staticmethod
    def _mark_messages_read(messages, private_chat):
        for message in messages:
            if message.get("read", True) is False:
                GLib.idle_add(private_chat.mark_read, message["id"])

    def _setup_history_container(self):
        history_container = self._view.builder.get_object("history_container")
        history_container.add(self._view)
Nico's avatar
Nico committed
189
190
        history_container.show_all()

Nico's avatar
Nico committed
191
192
193
194
    def _setup_chat_input(self):
        chat_input = self._view.builder.get_object("chat_input")
        self._chat_input_signal_id = chat_input.connect(
            "key-press-event", self._on_chat_input_activate
Nico's avatar
Nico committed
195
        )
Nico's avatar
Nico committed
196
197
        # TODO: Activate vscrollbar only if needed (to save space)
        # https://github.com/dino/dino/blob/231df1/main/src/ui/chat_input/chat_text_view.vala#L51
Nico's avatar
Nico committed
198
        chat_input.grab_focus()
Nico's avatar
Nico committed
199

200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    @staticmethod
    def _hide_chat_view(main_content_stack):
        chat_view = main_content_stack.get_child_by_name("chat_view")
        chat_view.hide()

    @staticmethod
    def _show_chat_placeholder(main_content_stack):
        chat_placeholder = main_content_stack.get_child_by_name(
            "chat_placeholder")
        chat_placeholder.show()

    def _unselect_contact(self):
        contacts_list_box = self._view.builder.get_object("contacts_list_box")
        contacts_list_box.unselect_all()

    def _hide_chat_menu_button(self):
        chat_menu_button = self._view.builder.get_object("chat_menu_button")
        chat_menu_button.hide()

    def _show_sidebar_box(self):
        main_window_leaflet = self._view.builder.get_object(
            "main_window_leaflet")
        sidebar_box = self._view.builder.get_object("sidebar_box")
        main_window_leaflet.set_visible_child(sidebar_box)

    def _delete_all_messages(self, widget, response_id):
        if response_id == Gtk.ResponseType.OK:
            private_chat = PrivateChat(APP().api, self._view.contact_id)
            private_chat.delete_all_messages()
            self._sidebar_presenter.refresh_contacts()
            self.close_private_chat()
        widget.destroy()

    def _delete_contact(self, widget, response_id):
        if response_id == Gtk.ResponseType.OK:
            Contacts(APP().api).delete(self._view.contact_id)
            self._sidebar_presenter.refresh_contacts()
            self.close_private_chat()
        widget.destroy()

    def _clear_history_container(self):
        history_container = self._view.builder.get_object("history_container")
        children = history_container.get_children()
        for child in children:
            child.destroy()

Nico's avatar
Nico committed
246
247
    def _disconnect_chat_input_signals(self):
        if not hasattr(self, "_chat_input_signal_id"):
Nico's avatar
Nico committed
248
            return
Nico's avatar
Nico committed
249
250
251
        chat_input = self._view.builder.get_object("chat_input")
        chat_input.disconnect(self._chat_input_signal_id)
        del self._chat_input_signal_id
252

Nico's avatar
Nico committed
253
    def _on_chat_input_activate(self, widget, event):
Nico's avatar
Nico committed
254
255
256
257
258
259
260
261
262
        # Return is pressed
        if Gdk.keyval_name(event.keyval) != 'Return':
            return False
        # Shift is not pressed
        if (event.state & Gdk.ModifierType.SHIFT_MASK) == Gdk.ModifierType.SHIFT_MASK:  # noqa
            return False
        # Text does not only contain whitespace
        if len(self._get_text_from_text_view(widget).strip()) == 0:
            return False
263
        self._send_message(widget)
Nico's avatar
Nico committed
264
        self._sidebar_presenter.refresh_contacts()
Nico's avatar
Nico committed
265
266
267
268
269
270
271
272
        return True

    @staticmethod
    def _get_text_from_text_view(widget):
        text_buffer = widget.get_buffer()
        start_iter = text_buffer.get_start_iter()
        end_iter = text_buffer.get_end_iter()
        return text_buffer.get_text(start_iter, end_iter, True)
273
274

    def _send_message(self, widget):
Nico's avatar
Nico committed
275
        message = self._get_text_from_text_view(widget)
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
        private_chat = PrivateChat(APP().api, self._view.contact_id)
        private_chat.send(message)

        # TODO: Move into briar_wrapper by emitting event on private_chat.send
        self._view.add_message(
            {
                "text": message,
                "local": True,
                "sent": False,
                "seen": False,

                # TODO: Remove once web events updating is implemented
                "no_stored_indicator": True,

                "timestamp": int(round(time.time() * 1000))
            })
Nico's avatar
Nico committed
292
        widget.get_buffer().set_text("")
293
        GLib.idle_add(self._view.scroll_to_bottom)