From 0dab21007d4ca90d331980b63cc4117d74115bdb Mon Sep 17 00:00:00 2001 From: iequidoo Date: Wed, 27 Aug 2025 07:20:52 -0300 Subject: [PATCH] api!: Contact::get_color(): Preserve address-based color hue for SELF 4010c60e7b6d00846fe23d688a4933ac4e9ca2df "feat: use key fingerprints for color generation" changes colors for contacts including SELF. Even if an avatar is set, the self-color is visible in e.g. replies to outgoing messages. This adds `Config::Selfcolor` and sets it in a migration. This doesn't preserve the old address-based color accurately because the old code generating colors is already dropped, so this only preserves the color angle (hue). --- deltachat-ffi/src/lib.rs | 3 ++- deltachat-jsonrpc/src/api/types/account.rs | 5 ++++- deltachat-jsonrpc/src/api/types/contact.rs | 2 +- deltachat-jsonrpc/src/api/types/message.rs | 6 ++++-- src/chat.rs | 2 +- src/config.rs | 6 +++++- src/config/config_tests.rs | 1 + src/contact.rs | 12 ++++++++--- src/contact/contact_tests.rs | 15 +++++++++++--- src/context/context_tests.rs | 1 + src/qr_code_generator.rs | 2 +- src/sql/migrations.rs | 24 ++++++++++++++++++++++ src/webxdc/maps_integration.rs | 2 +- 13 files changed, 66 insertions(+), 15 deletions(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index ff323b63c3..65487eb791 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -4311,7 +4311,8 @@ pub unsafe extern "C" fn dc_contact_get_color(contact: *mut dc_contact_t) -> u32 return 0; } let ffi_contact = &*contact; - ffi_contact.contact.get_color() + let ctx = &*ffi_contact.context; + block_on(ffi_contact.contact.get_color(ctx)).unwrap_or_log_default(ctx, "Failed get_color") } #[no_mangle] diff --git a/deltachat-jsonrpc/src/api/types/account.rs b/deltachat-jsonrpc/src/api/types/account.rs index a5f14240ca..1e786559bb 100644 --- a/deltachat-jsonrpc/src/api/types/account.rs +++ b/deltachat-jsonrpc/src/api/types/account.rs @@ -32,7 +32,10 @@ impl Account { let addr = ctx.get_config(Config::Addr).await?; let profile_image = ctx.get_config(Config::Selfavatar).await?; let color = color_int_to_hex_string( - Contact::get_by_id(ctx, ContactId::SELF).await?.get_color(), + Contact::get_by_id(ctx, ContactId::SELF) + .await? + .get_color(ctx) + .await?, ); let private_tag = ctx.get_config(Config::PrivateTag).await?; Ok(Account::Configured { diff --git a/deltachat-jsonrpc/src/api/types/contact.rs b/deltachat-jsonrpc/src/api/types/contact.rs index c4fb5663e7..2e415550ce 100644 --- a/deltachat-jsonrpc/src/api/types/contact.rs +++ b/deltachat-jsonrpc/src/api/types/contact.rs @@ -97,7 +97,7 @@ impl ContactObject { Ok(ContactObject { address: contact.get_addr().to_owned(), - color: color_int_to_hex_string(contact.get_color()), + color: color_int_to_hex_string(contact.get_color(context).await?), auth_name: contact.get_authname().to_owned(), status: contact.get_status().to_owned(), display_name: contact.get_display_name().to_owned(), diff --git a/deltachat-jsonrpc/src/api/types/message.rs b/deltachat-jsonrpc/src/api/types/message.rs index 398a6b018d..e0ac394fad 100644 --- a/deltachat-jsonrpc/src/api/types/message.rs +++ b/deltachat-jsonrpc/src/api/types/message.rs @@ -162,7 +162,9 @@ impl MessageObject { message_id: quote.get_id().to_u32(), chat_id: quote.get_chat_id().to_u32(), author_display_name: quote_author.get_display_name().to_owned(), - author_display_color: color_int_to_hex_string(quote_author.get_color()), + author_display_color: color_int_to_hex_string( + quote_author.get_color(context).await?, + ), override_sender_name: quote.get_override_sender_name(), image: if quote.get_viewtype() == Viewtype::Image || quote.get_viewtype() == Viewtype::Gif @@ -581,7 +583,7 @@ impl MessageSearchResult { id: msg_id.to_u32(), author_profile_image: profile_image, author_name, - author_color: color_int_to_hex_string(sender.get_color()), + author_color: color_int_to_hex_string(sender.get_color(context).await?), author_id: sender.id.to_u32(), chat_id: chat.id.to_u32(), chat_name: chat.get_name().to_owned(), diff --git a/src/chat.rs b/src/chat.rs index e9d63e6090..8feb348eca 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1807,7 +1807,7 @@ impl Chat { let contacts = get_chat_contacts(context, self.id).await?; if let Some(contact_id) = contacts.first() { if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { - color = contact.get_color(); + color = contact.get_color(context).await?; } } } else if !self.grpid.is_empty() { diff --git a/src/config.rs b/src/config.rs index 7bfebd868e..cc042ff91f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -136,6 +136,9 @@ pub enum Config { /// Own name to use in the `From:` field when sending messages. Displayname, + /// Own color to use in the avatar placeholder and replies to outgoing messages. + Selfcolor, + /// Own status to display, sent in message footer. Selfstatus, @@ -474,6 +477,7 @@ impl Config { | Self::MvboxMove | Self::ShowEmails | Self::Selfavatar + | Self::Selfcolor | Self::Selfstatus, ) } @@ -833,7 +837,7 @@ impl Context { } if matches!( key, - Config::Displayname | Config::Selfavatar | Config::PrivateTag + Config::Displayname | Config::Selfavatar | Config::Selfcolor | Config::PrivateTag ) { self.emit_event(EventType::AccountsItemChanged); } diff --git a/src/config/config_tests.rs b/src/config/config_tests.rs index fc0a296cf5..09e64f0577 100644 --- a/src/config/config_tests.rs +++ b/src/config/config_tests.rs @@ -245,6 +245,7 @@ async fn test_sync() -> Result<()> { Ok(()) } test_config_str(&alice0, &alice1, Config::Displayname, "Alice Sync").await?; + test_config_str(&alice0, &alice1, Config::Selfcolor, "255").await?; test_config_str(&alice0, &alice1, Config::Selfstatus, "My status").await?; assert!(alice0.get_config(Config::Selfavatar).await?.is_none()); diff --git a/src/contact.rs b/src/contact.rs index 2eb8bda663..497ca5858f 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1579,11 +1579,17 @@ impl Contact { /// or email address (for address-contacts) and can be used /// for an fallback avatar with white initials /// as well as for headlines in bubbles of group chats. - pub fn get_color(&self) -> u32 { + pub async fn get_color(&self, context: &Context) -> Result { + if self.id == ContactId::SELF { + if let Some(v) = context.get_config_opt_parsed(Config::Selfcolor).await? { + return Ok(v); + } + } + if let Some(fingerprint) = self.fingerprint() { - str_to_color(&fingerprint.hex()) + Ok(str_to_color(&fingerprint.hex())) } else { - str_to_color(&self.addr.to_lowercase()) + Ok(str_to_color(&self.addr.to_lowercase())) } } diff --git a/src/contact/contact_tests.rs b/src/contact/contact_tests.rs index 48f2f2af4b..542fa4c1c8 100644 --- a/src/contact/contact_tests.rs +++ b/src/contact/contact_tests.rs @@ -758,17 +758,26 @@ async fn test_lookup_id_by_addr() { async fn test_contact_get_color() -> Result<()> { let t = TestContext::new().await; let contact_id = Contact::create(&t, "name", "name@example.net").await?; - let color1 = Contact::get_by_id(&t, contact_id).await?.get_color(); + let color1 = Contact::get_by_id(&t, contact_id) + .await? + .get_color(&t) + .await?; assert_eq!(color1, 0x4947dc); let t = TestContext::new().await; let contact_id = Contact::create(&t, "prename name", "name@example.net").await?; - let color2 = Contact::get_by_id(&t, contact_id).await?.get_color(); + let color2 = Contact::get_by_id(&t, contact_id) + .await? + .get_color(&t) + .await?; assert_eq!(color2, color1); let t = TestContext::new().await; let contact_id = Contact::create(&t, "Name", "nAme@exAmple.NET").await?; - let color3 = Contact::get_by_id(&t, contact_id).await?.get_color(); + let color3 = Contact::get_by_id(&t, contact_id) + .await? + .get_color(&t) + .await?; assert_eq!(color3, color1); Ok(()) } diff --git a/src/context/context_tests.rs b/src/context/context_tests.rs index e80c17448f..ce6c1131ae 100644 --- a/src/context/context_tests.rs +++ b/src/context/context_tests.rs @@ -277,6 +277,7 @@ async fn test_get_info_completeness() { "mail_security", "notify_about_wrong_pw", "self_reporting_id", + "selfcolor", "selfstatus", "send_server", "send_user", diff --git a/src/qr_code_generator.rs b/src/qr_code_generator.rs index d8d6d4327a..886f5770ed 100644 --- a/src/qr_code_generator.rs +++ b/src/qr_code_generator.rs @@ -161,7 +161,7 @@ async fn self_info(context: &Context) -> Result<(Option>, String, String None => contact.get_addr().to_string(), }; let addr = contact.get_addr().to_string(); - let color = color_int_to_hex_string(contact.get_color()); + let color = color_int_to_hex_string(contact.get_color(context).await?); Ok((avatar, displayname, addr, color)) } diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index 4a7c40e911..16b234e22f 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -1261,6 +1261,30 @@ CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint); .await?; } + inc_and_check(&mut migration_version, 134)?; + if dbversion < migration_version { + let trans_fn = |t: &mut rusqlite::Transaction| { + let Some(addr): Option = t + .query_row( + "SELECT value FROM config WHERE keyname='configured_addr'", + (), + |row| row.get(0), + ) + .optional()? + else { + return Ok(()); + }; + let color = crate::color::str_to_color(&addr.to_lowercase()); + t.execute( + "INSERT OR IGNORE INTO config (keyname, value) VALUES ('selfcolor', ?)", + (color,), + )?; + Ok(()) + }; + sql.execute_migration_transaction(trans_fn, migration_version) + .await?; + } + let new_version = sql .get_raw_config_int(VERSION_CFG) .await? diff --git a/src/webxdc/maps_integration.rs b/src/webxdc/maps_integration.rs index 7f398e6e06..237d084a5b 100644 --- a/src/webxdc/maps_integration.rs +++ b/src/webxdc/maps_integration.rs @@ -112,7 +112,7 @@ pub(crate) async fn intercept_get_updates( hash_map::Entry::Vacant(e) => { let contact = Contact::get_by_id(context, location.contact_id).await?; let name = contact.get_display_name().to_string(); - let color = color_int_to_hex_string(contact.get_color()); + let color = color_int_to_hex_string(contact.get_color(context).await?); e.insert((name, color)).clone() } hash_map::Entry::Occupied(e) => e.get().clone(),