diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 5e1d9d28e..aee754322 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1569,7 +1569,7 @@ dc_array_t* dc_wait_next_msgs (dc_context_t* context); * (read receipts aren't sent for noticed messages). * * Calling this function usually results in the event #DC_EVENT_MSGS_NOTICED. - * See also dc_markseen_msgs(). + * See also dc_markseen_msgs() and dc_markfresh_chat(). * * @memberof dc_context_t * @param context The context object as returned from dc_context_new(). @@ -1578,6 +1578,29 @@ dc_array_t* dc_wait_next_msgs (dc_context_t* context); void dc_marknoticed_chat (dc_context_t* context, uint32_t chat_id); +/** + * Mark the last incoming message in chat as _fresh_. + * + * UI can use this to offer a "mark unread" option, + * so that already noticed chats (see dc_marknoticed_chat()) get a badge counter again. + * + * dc_get_fresh_msg_cnt() and dc_get_fresh_msgs() usually is increased by one afterwards. + * + * #DC_EVENT_MSGS_CHANGED is fired as usual, + * however, #DC_EVENT_INCOMING_MSG is _not_ fired again. + * This is to not add complexity to incoming messages code, + * e.g. UI usually does not add notifications for manually unread chats. + * If the UI wants to update system badge counters, + * they should do so directly after calling dc_markfresh_chat(). + * + * @memberof dc_context_t + * @param context The context object as returned from dc_context_new(). + * @param chat_id The chat ID of which messages should be marked as fresh. + * If the chat does not have incoming messages, nothing happens. + */ +void dc_markfresh_chat (dc_context_t* context, uint32_t chat_id); + + /** * Returns all message IDs of the given types in a given chat or any chat. * Typically used to show a gallery. diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 20be73db1..9a204d3cf 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1521,6 +1521,23 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id }) } +#[no_mangle] +pub unsafe extern "C" fn dc_markfresh_chat(context: *mut dc_context_t, chat_id: u32) { + if context.is_null() { + eprintln!("ignoring careless call to dc_markfresh_chat()"); + return; + } + let ctx = &*context; + + block_on(async move { + chat::markfresh_chat(ctx, ChatId::new(chat_id)) + .await + .context("Failed markfresh chat") + .log_err(ctx) + .unwrap_or(()) + }) +} + fn from_prim(s: S) -> Option where T: FromPrimitive, diff --git a/src/chat.rs b/src/chat.rs index e402a3753..ee3c260e4 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -3400,6 +3400,38 @@ pub(crate) async fn mark_old_messages_as_noticed( Ok(()) } +/// Marks last incoming message in a chat as fresh. +pub async fn markfresh_chat(context: &Context, chat_id: ChatId) -> Result<()> { + let affected_rows = context + .sql + .execute( + "UPDATE msgs + SET state=?1 + WHERE id=(SELECT id + FROM msgs + WHERE state IN (?1, ?2, ?3) AND hidden=0 AND chat_id=?4 + ORDER BY timestamp DESC, id DESC + LIMIT 1) + AND state!=?1", + ( + MessageState::InFresh, + MessageState::InNoticed, + MessageState::InSeen, + chat_id, + ), + ) + .await?; + + if affected_rows == 0 { + return Ok(()); + } + + context.emit_msgs_changed_without_msg_id(chat_id); + chatlist_events::emit_chatlist_item_changed(context, chat_id); + + Ok(()) +} + /// Returns all database message IDs of the given types. /// /// If `chat_id` is None, return messages from any chat. diff --git a/src/message.rs b/src/message.rs index ccd41ccfe..a58a3d8d1 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1938,6 +1938,17 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec) -> Result<()> && curr_param.get_cmd() == SystemMessage::Unknown && context.should_send_mdns().await? { + // Clear WantsMdn to not handle a MDN twice + // if the state later is InFresh again as markfresh_chat() was called. + // BccSelf MDN messages in the next branch may be sent twice for syncing. + context + .sql + .execute( + "UPDATE msgs SET param=? WHERE id=?", + (curr_param.clone().remove(Param::WantsMdn).to_string(), id), + ) + .await + .context("failed to clear WantsMdn")?; Some(curr_from_id) } else if context.get_config_bool(Config::BccSelf).await? { Some(ContactId::SELF) @@ -1955,6 +1966,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec) -> Result<()> .context("failed to insert into smtp_mdns")?; context.scheduler.interrupt_smtp().await; } + if !curr_hidden { updated_chat_ids.insert(curr_chat_id); }