mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
feat(notifications): show relative time in control center history
This commit is contained in:
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user