aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-26 15:33:00 +0200
committerDanilo M. <danix@danix.xyz>2026-08-26 15:33:00 +0200
commit39a0cce152dda3b3d828c6145f02c3db9bbd7449 (patch)
tree695a862a9c01fad66cbe9bff542fedf810459415
parent656d70aef7cacff69d2616d2df0cbe8d6e4188f9 (diff)
downloadqtmaildir-39a0cce152dda3b3d828c6145f02c3db9bbd7449.tar.gz
qtmaildir-39a0cce152dda3b3d828c6145f02c3db9bbd7449.zip
feat: choose an avatar fill and derive its colour
-rw-r--r--src/avatar.cpp22
-rw-r--r--tests/test_avatar.cpp28
2 files changed, 50 insertions, 0 deletions
diff --git a/src/avatar.cpp b/src/avatar.cpp
index 4cedae2..0e2b25f 100644
--- a/src/avatar.cpp
+++ b/src/avatar.cpp
@@ -75,4 +75,26 @@ QString initialsFor(const QString &displayName, const QString &address,
return QStringLiteral("??");
}
+Fill fillFor(const QString &displayName, bool isBusinessSender)
+{
+ // The list first: it is the user's explicit override and must beat the
+ // heuristic, or a listed sender could never be pinned.
+ if (isBusinessSender)
+ return Fill::TwoTone;
+ return displayName.trimmed().isEmpty() ? Fill::TwoTone : Fill::Identicon;
+}
+
+QColor colourFor(const QString &address)
+{
+ // The same construction TagColors::colourFor() uses for a tag with nothing
+ // configured: hashed so it is stable, at a fixed saturation and lightness
+ // so it cannot come out neon and cannot lose its contrast with the
+ // initials. The lightness differs from that function's deliberately: a
+ // chip carries dark text, a squircle carries white.
+ const QByteArray digest =
+ QCryptographicHash::hash(address.toUtf8(), QCryptographicHash::Md5);
+ const int hue = static_cast<quint8>(digest.at(0)) * 360 / 256;
+ return QColor::fromHsl(hue, 110, 95);
+}
+
} // namespace Avatar
diff --git a/tests/test_avatar.cpp b/tests/test_avatar.cpp
index 225b4f1..b53dfc1 100644
--- a/tests/test_avatar.cpp
+++ b/tests/test_avatar.cpp
@@ -30,6 +30,9 @@ private slots:
void bareAddressTakesLocalAndDomain();
void nothingUsableFallsBackToTheAccountLabel();
void initialsAreAlwaysTwoLetters();
+ void aDisplayNameMeansAPerson();
+ void theListOverridesADisplayName();
+ void aColourIsStablePerAddress();
};
void TestAvatar::twoWordNameTakesOneLetterFromEach()
@@ -91,5 +94,30 @@ void TestAvatar::initialsAreAlwaysTwoLetters()
}
}
+void TestAvatar::aDisplayNameMeansAPerson()
+{
+ // The case the user asked for by name: a corporate address that presents
+ // itself as a person reads as a person.
+ QCOMPARE(Avatar::fillFor(QStringLiteral("Ian Farrell"), false),
+ Avatar::Fill::Identicon);
+ QCOMPARE(Avatar::fillFor(QString(), false), Avatar::Fill::TwoTone);
+}
+
+void TestAvatar::theListOverridesADisplayName()
+{
+ // A listed address stays a business even when it sets a friendly name.
+ QCOMPARE(Avatar::fillFor(QStringLiteral("Cofidis"), true),
+ Avatar::Fill::TwoTone);
+}
+
+void TestAvatar::aColourIsStablePerAddress()
+{
+ const QColor first = Avatar::colourFor(QStringLiteral("a@example.org"));
+ const QColor again = Avatar::colourFor(QStringLiteral("a@example.org"));
+ QCOMPARE(first, again);
+ QVERIFY(first.isValid());
+ QVERIFY(Avatar::colourFor(QStringLiteral("b@example.org")) != first);
+}
+
QTEST_MAIN(TestAvatar)
#include "test_avatar.moc"