@@ -26,6 +26,7 @@ use matrix_sdk_common::{
26
26
AnyMessageEventContent , AnyStateEventContent ,
27
27
} ,
28
28
identifiers:: { EventId , UserId } ,
29
+ instant:: { Duration , Instant } ,
29
30
uuid:: Uuid ,
30
31
} ;
31
32
@@ -40,6 +41,9 @@ use matrix_sdk_base::crypto::AttachmentEncryptor;
40
41
#[ cfg( feature = "encryption" ) ]
41
42
use tracing:: instrument;
42
43
44
+ const TYPING_NOTICE_TIMEOUT : Duration = Duration :: from_secs ( 4 ) ;
45
+ const TYPING_NOTICE_RESEND_TIMEOUT : Duration = Duration :: from_secs ( 3 ) ;
46
+
43
47
/// A room in the joined state.
44
48
///
45
49
/// The `JoinedRoom` contains all methodes specific to a `Room` with type `RoomType::Joined`.
@@ -137,19 +141,23 @@ impl Joined {
137
141
Ok ( ( ) )
138
142
}
139
143
140
- /// Send a request to notify this room of a user typing.
144
+ /// Activate typing notice for this room.
145
+ ///
146
+ /// The typing notice remains active for 4s. It can be deactivate at any point by setting
147
+ /// typing to `false`. If this method is called while the typing notice is active nothing will happen.
148
+ /// This method can be called on every key stroke, since it will do nothing while typing is
149
+ /// active.
141
150
///
142
151
/// # Arguments
143
152
///
144
- /// * `typing` - Whether the user is typing, and how long .
153
+ /// * `typing` - Whether the user is typing or has stopped typing .
145
154
///
146
155
/// # Examples
147
156
///
148
157
/// ```no_run
149
158
/// # use std::time::Duration;
150
159
/// # use matrix_sdk::{
151
160
/// # Client, SyncSettings,
152
- /// # api::r0::typing::create_typing_event::Typing,
153
161
/// # identifiers::room_id,
154
162
/// # };
155
163
/// # use futures::executor::block_on;
@@ -162,21 +170,47 @@ impl Joined {
162
170
/// # .get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost"))
163
171
/// # .unwrap();
164
172
/// # room
165
- /// .typing_notice(Typing::Yes(Duration::from_secs(4)) )
173
+ /// .typing_notice(true )
166
174
/// .await
167
175
/// .expect("Can't get devices from server");
168
176
/// # });
169
177
///
170
178
/// ```
171
- pub async fn typing_notice ( & self , typing : impl Into < Typing > ) -> Result < ( ) > {
172
- // TODO: don't send a request if a typing notice is being sent or is already active
173
- let request = TypingRequest :: new (
174
- self . inner . own_user_id ( ) ,
175
- self . inner . room_id ( ) ,
176
- typing. into ( ) ,
177
- ) ;
179
+ pub async fn typing_notice ( & self , typing : bool ) -> Result < ( ) > {
180
+ // Only send a request to the homeserver if the old timeout has elapsed or the typing
181
+ // notice changed state within the TYPING_NOTICE_TIMEOUT
182
+ let send =
183
+ if let Some ( typing_time) = self . client . typing_notice_times . get ( self . inner . room_id ( ) ) {
184
+ if typing_time. elapsed ( ) > TYPING_NOTICE_RESEND_TIMEOUT {
185
+ // We always reactivate the typing notice if typing is true or we may need to
186
+ // deactivate it if it's currently active if typing is false
187
+ typing || typing_time. elapsed ( ) <= TYPING_NOTICE_TIMEOUT
188
+ } else {
189
+ // Only send a request when we need to deactivate typing
190
+ !typing
191
+ }
192
+ } else {
193
+ // Typing notice is currently deactivated, therefore, send a request only when it's
194
+ // about to be activated
195
+ typing
196
+ } ;
197
+
198
+ if send {
199
+ let typing = if typing {
200
+ self . client
201
+ . typing_notice_times
202
+ . insert ( self . inner . room_id ( ) . clone ( ) , Instant :: now ( ) ) ;
203
+ Typing :: Yes ( TYPING_NOTICE_TIMEOUT )
204
+ } else {
205
+ self . client . typing_notice_times . remove ( self . inner . room_id ( ) ) ;
206
+ Typing :: No
207
+ } ;
208
+
209
+ let request =
210
+ TypingRequest :: new ( self . inner . own_user_id ( ) , self . inner . room_id ( ) , typing) ;
211
+ self . client . send ( request, None ) . await ?;
212
+ }
178
213
179
- self . client . send ( request, None ) . await ?;
180
214
Ok ( ( ) )
181
215
}
182
216
0 commit comments