feat: a way to add a device message as InNoticed

This is useful for adding the same device message on multiple accounts,
which is what we do at least on Desktop currently:
c50896f1d5/packages/frontend/src/deviceMessages.ts (L35-L44).

The problem on desktop is that we then have to `rpc.marknoticedChat()`
so that the user isn't forced to read the same message
on all their accounts:
https://github.com/deltachat/deltachat-desktop/issues/3934.

However, that, in turn, is now causing problems
in our notification clearing logic: marking a chat as noticed
is supposed to remove the per-account "N messages in M chats"
notification, but we only want to do that when the user
actually reads a message on the account and,
whereas we do this `marknoticedChat()` for device messages
on app startup for all accounts. See the TODO in
https://github.com/deltachat/deltachat-desktop/pull/6154.
This commit is contained in:
WofWca
2026-07-06 22:22:27 +04:00
parent 446cdabd21
commit d1f8da34c5
4 changed files with 69 additions and 7 deletions

View File

@@ -53,7 +53,7 @@ use types::chat::FullChat;
use types::contact::{ContactObject, VcardContact};
use types::events::Event;
use types::http::HttpResponse;
use types::message::{MessageData, MessageObject, MessageReadReceipt};
use types::message::{DeviceMessageData, MessageData, MessageObject, MessageReadReceipt};
use types::notify_state::JsonrpcNotifyState;
use types::provider_info::ProviderInfo;
use types::reactions::JsonrpcReactions;
@@ -1229,18 +1229,27 @@ impl CommandApi {
&self,
account_id: u32,
label: String,
msg: Option<MessageData>,
msg: Option<DeviceMessageData>,
) -> Result<Option<u32>> {
let ctx = self.get_context(account_id).await?;
if let Some(msg) = msg {
let mut message = msg.create_message(&ctx).await?;
let message_id =
deltachat::chat::add_device_msg(&ctx, Some(&label), Some(&mut message)).await?;
let mut message = msg.message_data.create_message(&ctx).await?;
if let Some(state) = msg.message_state {
deltachat::chat::set_device_msg_state(&mut message, state.into());
}
let message_id = deltachat::chat::add_device_msg_with_importance(
&ctx,
Some(&label),
Some(&mut message),
false,
)
.await?;
if !message_id.is_unset() {
return Ok(Some(message_id.to_u32()));
}
} else {
deltachat::chat::add_device_msg(&ctx, Some(&label), None).await?;
deltachat::chat::add_device_msg_with_importance(&ctx, Some(&label), None, false)
.await?;
}
Ok(None)
}

View File

@@ -661,6 +661,42 @@ impl MessageData {
}
}
#[derive(Deserialize, Serialize, TypeDef, schemars::JsonSchema)]
#[repr(u32)]
pub enum DeviceMessageState {
// Variants that are not really wanted for device messages are omitted,
// to avoid misuse.
// Undefined,
InFresh,
InNoticed,
// InSeen,
// OutDraft,
// OutPending,
// OutFailed,
// OutDelivered,
// OutMdnRcvd,
}
impl From<DeviceMessageState> for deltachat::message::MessageState {
fn from(message_state: DeviceMessageState) -> Self {
use deltachat::message::MessageState;
match message_state {
DeviceMessageState::InFresh => MessageState::InFresh,
DeviceMessageState::InNoticed => MessageState::InNoticed,
}
}
}
#[derive(Deserialize, Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct DeviceMessageData {
#[serde(flatten)]
pub message_data: MessageData,
#[serde(default)]
pub message_state: Option<DeviceMessageState>,
}
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct MessageReadReceipt {

View File

@@ -4810,10 +4810,20 @@ pub(crate) async fn get_chat_id_by_grpid(
.await
}
/// Intended to be used only for messages passed to
/// [`add_device_msg_with_importance`].
pub fn set_device_msg_state(msg: &mut Message, state: MessageState) {
msg.state = state;
}
/// Adds a message to device chat.
///
/// Optional `label` can be provided to ensure that message is added only once.
/// If `important` is true, a notification will be sent.
///
/// If message state is [`MessageState::Undefined`],
/// the message will be added to the DB as [`MessageState::InFresh`].
/// To set message state, you may utilize [`set_device_msg_state`].
#[expect(clippy::arithmetic_side_effects)]
pub async fn add_device_msg_with_importance(
context: &Context,
@@ -4850,7 +4860,11 @@ pub async fn add_device_msg_with_importance(
msg.timestamp_sort = last_msg_time + 1;
}
prepare_msg_blob(context, msg).await?;
let state = MessageState::InFresh;
let state = if msg.state == MessageState::Undefined {
MessageState::InFresh
} else {
msg.state
};
let row_id = context
.sql
.insert(

View File

@@ -854,6 +854,7 @@ async fn test_add_device_msg_unlabelled() {
// add two device-messages
let mut msg1 = Message::new_text("first message".to_string());
msg1.state = MessageState::InNoticed;
let msg1_id = add_device_msg(&t, None, Some(&mut msg1)).await;
assert!(msg1_id.is_ok());
@@ -869,12 +870,14 @@ async fn test_add_device_msg_unlabelled() {
assert_eq!(msg1.text, "first message");
assert_eq!(msg1.from_id, ContactId::DEVICE);
assert_eq!(msg1.to_id, ContactId::SELF);
assert_eq!(msg1.get_state(), MessageState::InNoticed);
assert!(!msg1.is_info());
let msg2 = message::Message::load_from_db(&t, msg2_id.unwrap()).await;
assert!(msg2.is_ok());
let msg2 = msg2.unwrap();
assert_eq!(msg2.text, "second message");
assert_eq!(msg2.get_state(), MessageState::InFresh);
// check device chat
assert_eq!(msg2.chat_id.get_msg_cnt(&t).await.unwrap(), 2);