Skip to content

Commit 19b4520

Browse files
committed
boxes: Configure private_box_view to send typing status.
This commit captures the user's typing status using urwid's connect_signal(), observing for changes in the msg_write_box. The msg_write_box is connected to a function that sends 'start' events with a limit on how frequently the functions can be called. Idleness is tracked with a method which is initiated when a start event is sent and checks for a possible halt. The corresponding wait periods after the user starts and stops typing are used to restrict calling the function excessively. Fixes #593.
1 parent c78db0c commit 19b4520

File tree

1 file changed

+53
-3
lines changed

1 file changed

+53
-3
lines changed

zulipterminal/ui_tools/boxes.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import re
22
import unicodedata
33
from collections import OrderedDict, defaultdict
4-
from datetime import date, datetime
4+
from datetime import date, datetime, timedelta
55
from sys import platform
6-
from time import ctime, time
6+
from time import ctime, sleep, time
77
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
88
from urllib.parse import urljoin, urlparse
99

@@ -23,7 +23,7 @@
2323
STREAM_TOPIC_SEPARATOR, TIME_MENTION_MARKER,
2424
)
2525
from zulipterminal.helper import (
26-
Message, format_string, match_emoji, match_group, match_stream,
26+
Message, asynch, format_string, match_emoji, match_group, match_stream,
2727
match_topics, match_user,
2828
)
2929
from zulipterminal.ui_tools.buttons import EditModeButton
@@ -41,6 +41,9 @@ def __init__(self, view: Any) -> None:
4141
self.stream_id = None # type: Optional[int]
4242
self.recipient_user_ids = [] # type: List[int]
4343
self.msg_body_edit_enabled = True
44+
self.send_next_typing_update = datetime.now()
45+
self.last_key_update = datetime.now()
46+
self.idle_status_tracking = False
4447
self.FOCUS_CONTAINER_HEADER = 0
4548
self.FOCUS_HEADER_BOX_RECIPIENT = 0
4649
self.FOCUS_HEADER_BOX_STREAM = 1
@@ -62,13 +65,24 @@ def main_view(self, new: bool) -> Any:
6265
def set_editor_mode(self) -> None:
6366
self.view.controller.enter_editor_mode_with(self)
6467

68+
def send_stop_typing_status(self) -> None:
69+
# Send 'stop' updates only for PM narrows.
70+
if self.to_write_box:
71+
self.model.send_typing_status_by_user_ids(
72+
self.recipient_user_ids,
73+
status='stop'
74+
)
75+
self.send_next_typing_update = datetime.now()
76+
self.idle_status_tracking = False
77+
6578
def private_box_view(self, button: Any=None, email: str='',
6679
recipient_user_ids: Optional[List[int]]=None) -> None:
6780
self.set_editor_mode()
6881
if recipient_user_ids:
6982
self.recipient_user_ids = recipient_user_ids
7083
if email == '' and button is not None:
7184
email = button.email
85+
self.send_next_typing_update = datetime.now()
7286
self.to_write_box = ReadlineEdit("To: ", edit_text=email)
7387
self.msg_write_box = ReadlineEdit(multiline=True)
7488
self.msg_write_box.enable_autocomplete(
@@ -88,6 +102,40 @@ def private_box_view(self, button: Any=None, email: str='',
88102
]
89103
self.focus_position = self.FOCUS_CONTAINER_MESSAGE
90104

105+
# Typing status is sent in regular intervals to limit the number of
106+
# notifications sent. Idleness should also prompt a notification.
107+
# Refer to https://zulip.com/api/set-typing-status for the protocol
108+
# on typing notifications sent by clients.
109+
TYPING_STARTED_WAIT_PERIOD = 10
110+
TYPING_STOPPED_WAIT_PERIOD = 5
111+
112+
start_period_delta = timedelta(seconds=TYPING_STARTED_WAIT_PERIOD)
113+
stop_period_delta = timedelta(seconds=TYPING_STOPPED_WAIT_PERIOD)
114+
115+
def on_type_send_status(edit: object, new_edit_text: str) -> None:
116+
if new_edit_text:
117+
self.last_key_update = datetime.now()
118+
if self.last_key_update > self.send_next_typing_update:
119+
self.model.send_typing_status_by_user_ids(
120+
self.recipient_user_ids, status='start')
121+
self.send_next_typing_update += start_period_delta
122+
# Initiate tracker function only if it isn't already
123+
# initiated.
124+
if not self.idle_status_tracking:
125+
self.idle_status_tracking = True
126+
track_idleness_and_update_status()
127+
128+
@asynch
129+
def track_idleness_and_update_status() -> None:
130+
while datetime.now() < self.last_key_update + stop_period_delta:
131+
idle_check_time = (self.last_key_update
132+
+ stop_period_delta
133+
- datetime.now())
134+
sleep(idle_check_time.total_seconds())
135+
self.send_stop_typing_status()
136+
137+
urwid.connect_signal(self.msg_write_box, 'change', on_type_send_status)
138+
91139
def stream_box_view(self, stream_id: int, caption: str='', title: str='',
92140
) -> None:
93141
self.set_editor_mode()
@@ -351,6 +399,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
351399
self.view.set_footer_text()
352400

353401
if is_command_key('SEND_MESSAGE', key):
402+
self.send_stop_typing_status()
354403
if not self.to_write_box:
355404
if re.fullmatch(r'\s*', self.title_write_box.edit_text):
356405
topic = '(no topic)'
@@ -390,6 +439,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
390439
elif is_command_key('GO_BACK', key):
391440
self.msg_edit_id = None
392441
self.msg_body_edit_enabled = True
442+
self.send_stop_typing_status()
393443
self.view.controller.exit_editor_mode()
394444
self.main_view(False)
395445
self.view.middle_column.set_focus('body')

0 commit comments

Comments
 (0)