diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 0b7d06c5..a601312d 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -138,6 +138,7 @@ pub struct Client { /// Lock making sure we're only doing one key claim request at a time. key_claim_lock: Arc>, pub(crate) members_request_locks: DashMap>>, + pub(crate) typing_notice_times: DashMap, } #[cfg(not(tarpaulin_include))] @@ -394,6 +395,7 @@ impl Client { #[cfg(feature = "encryption")] key_claim_lock: Arc::new(Mutex::new(())), members_request_locks: DashMap::new(), + typing_notice_times: DashMap::new(), }) } @@ -1669,7 +1671,6 @@ impl Client { /// # use std::{path::PathBuf, time::Duration}; /// # use matrix_sdk::{ /// # Client, SyncSettings, - /// # api::r0::typing::create_typing_event::Typing, /// # identifiers::room_id, /// # }; /// # use futures::executor::block_on; @@ -1747,7 +1748,6 @@ impl Client { /// # use std::{path::PathBuf, time::Duration}; /// # use matrix_sdk::{ /// # Client, SyncSettings, - /// # api::r0::typing::create_typing_event::Typing, /// # identifiers::room_id, /// # }; /// # use futures::executor::block_on; @@ -1802,7 +1802,7 @@ mod test { api::r0::{ account::register::Request as RegistrationRequest, directory::get_public_rooms_filtered::Request as PublicRoomsFilterRequest, - membership::Invite3pid, typing::create_typing_event::Typing, uiaa::AuthData, + membership::Invite3pid, uiaa::AuthData, }, assign, directory::Filter, @@ -2451,9 +2451,7 @@ mod test { .get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost")) .unwrap(); - room.typing_notice(Typing::Yes(std::time::Duration::from_secs(1))) - .await - .unwrap(); + room.typing_notice(true).await.unwrap(); } #[tokio::test] diff --git a/matrix_sdk/src/room/joined.rs b/matrix_sdk/src/room/joined.rs index 42cb8fb3..e4ee82b5 100644 --- a/matrix_sdk/src/room/joined.rs +++ b/matrix_sdk/src/room/joined.rs @@ -26,6 +26,7 @@ use matrix_sdk_common::{ AnyMessageEventContent, AnyStateEventContent, }, identifiers::{EventId, UserId}, + instant::{Duration, Instant}, uuid::Uuid, }; @@ -40,6 +41,9 @@ use matrix_sdk_base::crypto::AttachmentEncryptor; #[cfg(feature = "encryption")] use tracing::instrument; +const TYPING_NOTICE_TIMEOUT: Duration = Duration::from_secs(4); +const TYPING_NOTICE_RESEND_TIMEOUT: Duration = Duration::from_secs(3); + /// A room in the joined state. /// /// The `JoinedRoom` contains all methodes specific to a `Room` with type `RoomType::Joined`. @@ -137,11 +141,16 @@ impl Joined { Ok(()) } - /// Send a request to notify this room of a user typing. + /// Activate typing notice for this room. + /// + /// The typing notice remains active for 4s. It can be deactivate at any point by setting + /// typing to `false`. If this method is called while the typing notice is active nothing will happen. + /// This method can be called on every key stroke, since it will do nothing while typing is + /// active. /// /// # Arguments /// - /// * `typing` - Whether the user is typing, and how long. + /// * `typing` - Whether the user is typing or has stopped typing. /// /// # Examples /// @@ -149,7 +158,6 @@ impl Joined { /// # use std::time::Duration; /// # use matrix_sdk::{ /// # Client, SyncSettings, - /// # api::r0::typing::create_typing_event::Typing, /// # identifiers::room_id, /// # }; /// # use futures::executor::block_on; @@ -162,21 +170,47 @@ impl Joined { /// # .get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost")) /// # .unwrap(); /// # room - /// .typing_notice(Typing::Yes(Duration::from_secs(4))) + /// .typing_notice(true) /// .await /// .expect("Can't get devices from server"); /// # }); /// /// ``` - pub async fn typing_notice(&self, typing: impl Into) -> Result<()> { - // TODO: don't send a request if a typing notice is being sent or is already active - let request = TypingRequest::new( - self.inner.own_user_id(), - self.inner.room_id(), - typing.into(), - ); + pub async fn typing_notice(&self, typing: bool) -> Result<()> { + // Only send a request to the homeserver if the old timeout has elapsed or the typing + // notice changed state within the TYPING_NOTICE_TIMEOUT + let send = + if let Some(typing_time) = self.client.typing_notice_times.get(self.inner.room_id()) { + if typing_time.elapsed() > TYPING_NOTICE_RESEND_TIMEOUT { + // We always reactivate the typing notice if typing is true or we may need to + // deactivate it if it's currently active if typing is false + typing || typing_time.elapsed() <= TYPING_NOTICE_TIMEOUT + } else { + // Only send a request when we need to deactivate typing + !typing + } + } else { + // Typing notice is currently deactivated, therefore, send a request only when it's + // about to be activated + typing + }; + + if send { + let typing = if typing { + self.client + .typing_notice_times + .insert(self.inner.room_id().clone(), Instant::now()); + Typing::Yes(TYPING_NOTICE_TIMEOUT) + } else { + self.client.typing_notice_times.remove(self.inner.room_id()); + Typing::No + }; + + let request = + TypingRequest::new(self.inner.own_user_id(), self.inner.room_id(), typing); + self.client.send(request, None).await?; + } - self.client.send(request, None).await?; Ok(()) }