feat(notifications): show relative time in control center history

This commit is contained in:
Ly-sec
2026-05-03 11:13:22 +02:00
parent f83ba97182
commit c8c5830507
4 changed files with 63 additions and 23 deletions
+15 -2
View File
@@ -5,12 +5,14 @@
#include "notification/notification_manager.h"
#include "render/core/renderer.h"
#include "shell/panel/panel_manager.h"
#include "time/time_format.h"
#include "ui/controls/button.h"
#include "ui/controls/flex.h"
#include "ui/controls/label.h"
#include "ui/controls/scroll_view.h"
#include "ui/palette.h"
#include <chrono>
#include <cmath>
#include <memory>
#include <vector>
@@ -159,6 +161,7 @@ void NotificationsTab::onClose() {
m_expandedIds.clear();
m_lastSerial = 0;
m_lastWidth = -1.0f;
m_lastRelativeTimeSlot = -1;
}
void NotificationsTab::clearAllNotifications() {
@@ -214,7 +217,10 @@ void NotificationsTab::rebuild(Renderer& renderer, float width) {
}
const std::uint64_t serial = m_notifications != nullptr ? m_notifications->changeSerial() : 0;
if (serial == m_lastSerial && std::abs(width - m_lastWidth) < 0.5f) {
const std::int64_t relativeSlot =
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count() /
15;
if (serial == m_lastSerial && std::abs(width - m_lastWidth) < 0.5f && relativeSlot == m_lastRelativeTimeSlot) {
return;
}
@@ -254,6 +260,7 @@ void NotificationsTab::rebuild(Renderer& renderer, float width) {
m_list->addChild(std::move(empty));
m_lastSerial = serial;
m_lastWidth = width;
m_lastRelativeTimeSlot = relativeSlot;
return;
}
@@ -285,7 +292,12 @@ void NotificationsTab::rebuild(Renderer& renderer, float width) {
header->setGap(Style::spaceSm * scale);
auto meta = std::make_unique<Label>();
meta->setText(it->notification.appName + "" + statusText(*it));
std::string metaLine = it->notification.appName + "" + formatElapsedSince(it->notification.receivedTime);
if (!it->active) {
metaLine += "";
metaLine += statusText(*it);
}
meta->setText(std::move(metaLine));
meta->setCaptionStyle();
meta->setFontSize(Style::fontSizeCaption * scale);
meta->setColor(colorSpecFromRole(statusColorRole(*it)));
@@ -351,4 +363,5 @@ void NotificationsTab::rebuild(Renderer& renderer, float width) {
m_lastSerial = serial;
m_lastWidth = width;
m_lastRelativeTimeSlot = relativeSlot;
}
@@ -33,4 +33,6 @@ private:
std::unordered_set<uint32_t> m_expandedIds;
std::uint64_t m_lastSerial = 0;
float m_lastWidth = -1.0f;
/// Wall-clock coarse slot so relative times (e.g. "2 min ago") refresh without churning every frame.
std::int64_t m_lastRelativeTimeSlot = -1;
};
+43 -21
View File
@@ -12,6 +12,43 @@
#include <optional>
#include <string_view>
namespace {
std::string formatAgeSeconds(std::int64_t secs,
std::optional<std::chrono::system_clock::time_point> calendarAfterSixDays) {
using namespace std::chrono;
if (secs < 0) {
secs = 0;
}
if (secs < 60) {
return i18n::tr("time.relative.just-now");
}
if (secs < 3600) {
const long mins = secs / 60;
return i18n::trp("time.relative.minutes-ago", mins);
}
if (secs < 86400) {
const long hrs = secs / 3600;
return i18n::trp("time.relative.hours-ago", hrs);
}
if (secs < 7 * 86400) {
const long days = secs / 86400;
return i18n::trp("time.relative.days-ago", days);
}
if (calendarAfterSixDays.has_value()) {
const std::time_t rawTime = std::chrono::system_clock::to_time_t(*calendarAfterSixDays);
std::tm localTime{};
localtime_r(&rawTime, &localTime);
char buffer[32];
std::strftime(buffer, sizeof(buffer), "%b %e", &localTime);
return buffer;
}
const long days = static_cast<long>(secs / 86400);
return i18n::trp("time.relative.days-ago", days);
}
} // namespace
namespace {
bool shouldUseStrftimeCompat(std::string_view fmt) {
@@ -156,28 +193,13 @@ std::string formatFileTime(const std::filesystem::file_time_type& time) {
std::string formatTimeAgo(std::chrono::system_clock::time_point tp) {
using namespace std::chrono;
const auto secs = duration_cast<seconds>(system_clock::now() - tp).count();
return formatAgeSeconds(secs, tp);
}
if (secs < 60) {
return i18n::tr("time.relative.just-now");
}
if (secs < 3600) {
const long mins = secs / 60;
return i18n::trp("time.relative.minutes-ago", mins);
}
if (secs < 86400) {
const long hrs = secs / 3600;
return i18n::trp("time.relative.hours-ago", hrs);
}
if (secs < 7 * 86400) {
const long days = secs / 86400;
return i18n::trp("time.relative.days-ago", days);
}
const std::time_t rawTime = system_clock::to_time_t(tp);
std::tm localTime{};
localtime_r(&rawTime, &localTime);
char buffer[32];
std::strftime(buffer, sizeof(buffer), "%b %e", &localTime);
return buffer;
std::string formatElapsedSince(std::chrono::steady_clock::time_point since) {
using namespace std::chrono;
const auto secs = duration_cast<seconds>(steady_clock::now() - since).count();
return formatAgeSeconds(secs, std::nullopt);
}
std::string formatDuration(std::chrono::seconds duration) {
+3
View File
@@ -7,6 +7,9 @@
std::string formatTimeAgo(std::chrono::system_clock::time_point tp);
// Same wording as formatTimeAgo, but duration is computed from steady_clock (e.g. Notification::receivedTime).
[[nodiscard]] std::string formatElapsedSince(std::chrono::steady_clock::time_point since);
// Formats a duration as "{d}d {h}h {m}m" / "{h}h {m}m" / "{m}m" / "<1m".
[[nodiscard]] std::string formatDuration(std::chrono::seconds duration);