1
1
import re
2
2
import unicodedata
3
3
from collections import OrderedDict , defaultdict
4
- from datetime import date , datetime
4
+ from datetime import date , datetime , timedelta
5
5
from sys import platform
6
- from time import ctime , time
6
+ from time import ctime , sleep , time
7
7
from typing import Any , Callable , Dict , List , Optional , Tuple , Union
8
8
from urllib .parse import urljoin , urlparse
9
9
23
23
STREAM_TOPIC_SEPARATOR , TIME_MENTION_MARKER ,
24
24
)
25
25
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 ,
27
27
match_topics , match_user ,
28
28
)
29
29
from zulipterminal .ui_tools .buttons import EditModeButton
@@ -41,6 +41,9 @@ def __init__(self, view: Any) -> None:
41
41
self .stream_id = None # type: Optional[int]
42
42
self .recipient_user_ids = [] # type: List[int]
43
43
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
44
47
self .FOCUS_CONTAINER_HEADER = 0
45
48
self .FOCUS_HEADER_BOX_RECIPIENT = 0
46
49
self .FOCUS_HEADER_BOX_STREAM = 1
@@ -62,13 +65,24 @@ def main_view(self, new: bool) -> Any:
62
65
def set_editor_mode (self ) -> None :
63
66
self .view .controller .enter_editor_mode_with (self )
64
67
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
+
65
78
def private_box_view (self , button : Any = None , email : str = '' ,
66
79
recipient_user_ids : Optional [List [int ]]= None ) -> None :
67
80
self .set_editor_mode ()
68
81
if recipient_user_ids :
69
82
self .recipient_user_ids = recipient_user_ids
70
83
if email == '' and button is not None :
71
84
email = button .email
85
+ self .send_next_typing_update = datetime .now ()
72
86
self .to_write_box = ReadlineEdit ("To: " , edit_text = email )
73
87
self .msg_write_box = ReadlineEdit (multiline = True )
74
88
self .msg_write_box .enable_autocomplete (
@@ -88,6 +102,40 @@ def private_box_view(self, button: Any=None, email: str='',
88
102
]
89
103
self .focus_position = self .FOCUS_CONTAINER_MESSAGE
90
104
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
+
91
139
def stream_box_view (self , stream_id : int , caption : str = '' , title : str = '' ,
92
140
) -> None :
93
141
self .set_editor_mode ()
@@ -351,6 +399,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
351
399
self .view .set_footer_text ()
352
400
353
401
if is_command_key ('SEND_MESSAGE' , key ):
402
+ self .send_stop_typing_status ()
354
403
if not self .to_write_box :
355
404
if re .fullmatch (r'\s*' , self .title_write_box .edit_text ):
356
405
topic = '(no topic)'
@@ -390,6 +439,7 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
390
439
elif is_command_key ('GO_BACK' , key ):
391
440
self .msg_edit_id = None
392
441
self .msg_body_edit_enabled = True
442
+ self .send_stop_typing_status ()
393
443
self .view .controller .exit_editor_mode ()
394
444
self .main_view (False )
395
445
self .view .middle_column .set_focus ('body' )
0 commit comments