Revert "Improve time handling in NeoChat"

This reverts commit 92c58b0ea0.
This commit is contained in:
James Graham
2026-01-25 13:07:53 +00:00
parent 92c58b0ea0
commit f22cafbce1
17 changed files with 172 additions and 191 deletions

View File

@@ -40,6 +40,7 @@ private Q_SLOTS:
void nullSingleLineDisplayName();
void time();
void nullTime();
void timeString();
void highlighted();
void nullHighlighted();
void hidden();
@@ -99,12 +100,12 @@ void EventHandlerTest::time()
{
const auto event = room->messageEvents().at(0).get();
QCOMPARE(EventHandler::dateTime(room, event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
QCOMPARE(EventHandler::time(room, event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
QCOMPARE(room->pendingEvents().size(), 1);
const auto pendingIt = room->findPendingEvent(txID);
QCOMPARE(EventHandler::dateTime(room, pendingIt->event(), true), pendingIt->lastUpdated());
QCOMPARE(EventHandler::time(room, pendingIt->event(), true), pendingIt->lastUpdated());
room->discardMessage(txID);
QCOMPARE(room->pendingEvents().size(), 0);
@@ -113,10 +114,40 @@ void EventHandlerTest::time()
void EventHandlerTest::nullTime()
{
QTest::ignoreMessage(QtWarningMsg, "time called with room set to nullptr.");
QCOMPARE(EventHandler::dateTime(nullptr, nullptr), QDateTime());
QCOMPARE(EventHandler::time(nullptr, nullptr), QDateTime());
QTest::ignoreMessage(QtWarningMsg, "time called with event set to nullptr.");
QCOMPARE(EventHandler::dateTime(room, nullptr), QDateTime());
QCOMPARE(EventHandler::time(room, nullptr), QDateTime());
}
void EventHandlerTest::timeString()
{
const auto event = room->messageEvents().at(0).get();
KFormat format;
QCOMPARE(EventHandler::timeString(room, event, false),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, true),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s),
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::LocalTime)).toString(u"hh:mm"_s));
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
QCOMPARE(room->pendingEvents().size(), 1);
const auto pendingIt = room->findPendingEvent(txID);
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), false, QLocale::ShortFormat, true),
QLocale().toString(pendingIt->lastUpdated().toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), true, QLocale::ShortFormat, true),
format.formatRelativeDate(pendingIt->lastUpdated().toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), false, QLocale::LongFormat, true),
QLocale().toString(pendingIt->lastUpdated().toLocalTime().time(), QLocale::LongFormat));
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), true, QLocale::LongFormat, true),
format.formatRelativeDate(pendingIt->lastUpdated().toLocalTime().date(), QLocale::LongFormat));
room->discardMessage(txID);
QCOMPARE(room->pendingEvents().size(), 0);
}
void EventHandlerTest::highlighted()

View File

@@ -17,7 +17,6 @@ target_sources(LibNeoChat PRIVATE
filetransferpseudojob.cpp
filetype.cpp
linkpreviewer.cpp
neochatdatetime.cpp
roomlastmessageprovider.cpp
spacehierarchycache.cpp
texthandler.cpp

View File

@@ -93,7 +93,7 @@ QString EventHandler::singleLineAuthorDisplayname(const NeoChatRoom *room, const
return displayName;
}
NeoChatDateTime EventHandler::dateTime(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending)
QDateTime EventHandler::time(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending)
{
if (room == nullptr) {
qCWarning(EventHandling) << "time called with room set to nullptr.";
@@ -114,6 +114,25 @@ NeoChatDateTime EventHandler::dateTime(const NeoChatRoom *room, const Quotient::
return event->originTimestamp();
}
QString EventHandler::timeString(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool relative, QLocale::FormatType format, bool isPending)
{
auto ts = time(room, event, isPending);
if (ts.isValid()) {
if (relative) {
KFormat formatter;
return formatter.formatRelativeDate(ts.toLocalTime().date(), format);
} else {
return QLocale().toString(ts.toLocalTime().time(), format);
}
}
return {};
}
QString EventHandler::timeString(const NeoChatRoom *room, const Quotient::RoomEvent *event, const QString &format, bool isPending)
{
return time(room, event, isPending).toLocalTime().toString(format);
}
bool EventHandler::isHighlighted(const NeoChatRoom *room, const Quotient::RoomEvent *event)
{
if (room == nullptr) {

View File

@@ -7,8 +7,6 @@
#include <QString>
#include <Quotient/events/eventcontent.h>
#include "neochatdatetime.h"
namespace Quotient
{
namespace EventContent
@@ -66,7 +64,41 @@ public:
/**
* @brief Return a QDateTime object for the event timestamp.
*/
static NeoChatDateTime dateTime(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending = false);
static QDateTime time(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending = false);
/**
* @brief Return a QString for the event timestamp.
*
* This is intended to return a string that is read for display in the UI without
* any further manipulation required.
*
* @param relative whether the string is realtive to the current date, i.e.
* Yesterday or Wednesday, etc.
* @param format the QLocale::FormatType to use.
* @param isPending whether the event is pending as this cannot be derived from
* just the event object.
* @param lastUpdated the time the event was last updated locally as this cannot be
* obtained from the event.
*/
static QString timeString(const NeoChatRoom *room,
const Quotient::RoomEvent *event,
bool relative,
QLocale::FormatType format = QLocale::ShortFormat,
bool isPending = false);
/**
* @brief Return a QString for the event timestamp.
*
* This is intended to return a string that is read for display in the UI without
* any further manipulation required.
*
* @param format the format to use as a string.
* @param isPending whether the event is pending as this cannot be derived from
* just the event object.
* @param lastUpdated the time the event was last updated locally as this cannot be
* obtained from the event.
*/
static QString timeString(const NeoChatRoom *room, const Quotient::RoomEvent *event, const QString &format, bool isPending = false);
/**
* @brief Whether the event should be highlighted in the timeline.

View File

@@ -1,48 +0,0 @@
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "neochatdatetime.h"
#include <KFormat>
using namespace Qt::Literals::StringLiterals;
NeoChatDateTime::NeoChatDateTime(QDateTime dateTime)
: m_dateTime(dateTime)
{
}
QDateTime NeoChatDateTime::dateTime() const
{
return m_dateTime;
}
QString NeoChatDateTime::hourMinuteString() const
{
return m_dateTime.toLocalTime().toString(u"hh:mm"_s);
}
QString NeoChatDateTime::shortDateTime() const
{
return QLocale().toString(m_dateTime.toLocalTime(), QLocale::ShortFormat);
}
QString NeoChatDateTime::relativeDate() const
{
KFormat formatter;
return formatter.formatRelativeDate(m_dateTime.toLocalTime().date(), QLocale::ShortFormat);
}
QString NeoChatDateTime::relativeDateTime() const
{
KFormat formatter;
const auto relativePart = formatter.formatRelativeDate(m_dateTime.toLocalTime().date(), QLocale::ShortFormat);
return u"%1, %2"_s.arg(relativePart, hourMinuteString());
}
bool NeoChatDateTime::operator==(const QDateTime &right) const
{
return m_dateTime == right;
}
#include "moc_neochatdatetime.cpp"

View File

@@ -1,86 +0,0 @@
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QDateTime>
#include <QQmlEngine>
/**
* @class NeoChatDateTime
*
* This class is a helper for converting a QDateTime into the various format required in NeoChat.
*
* The intention is that this can be passed to QML and then the various Q_Properties
* can be called to get the date/time in the desired format reading for viewing in
* the UI.
*/
class NeoChatDateTime
{
Q_GADGET
QML_ELEMENT
/**
* @brief The base QDateTime used to generate the other values.
*/
Q_PROPERTY(QDateTime dateTime READ dateTime CONSTANT)
/**
* @brief The time formatted as "hh:mm".
*/
Q_PROPERTY(QString hourMinuteString READ hourMinuteString CONSTANT)
/**
* @brief The date and time formatted as per QLocale::ShortFormat for your locale.
*/
Q_PROPERTY(QString shortDateTime READ shortDateTime CONSTANT)
/**
* @brief The date formatted as relative to now.
*
* If the date falls within one week before or after the current date
* then a relative date string will be returned, such as:
* - Yesterday
* - Today
* - Tomorrow
* - Last Tuesday
* - Next Wednesday
*
* If the date falls outside this period then the format QLocale::ShortFormat
* for your locale is used.
*/
Q_PROPERTY(QString relativeDate READ relativeDate CONSTANT)
/**
* @brief The time and date formatted as relative to now.
*
* The format is "RelativeDate, hh::mm"
*
* If the date falls within one week before or after the current date
* then a relative date string will be returned, such as:
* - Yesterday
* - Today
* - Tomorrow
* - Last Tuesday
* - Next Wednesday
*
* If the date falls outside this period then the format QLocale::ShortFormat
* for your locale is used.
*/
Q_PROPERTY(QString relativeDateTime READ relativeDateTime CONSTANT)
public:
NeoChatDateTime(QDateTime dateTime = {});
QDateTime dateTime() const;
QString hourMinuteString() const;
QString shortDateTime() const;
QString relativeDate() const;
QString relativeDateTime() const;
bool operator==(const QDateTime &right) const;
private:
QDateTime m_dateTime;
};

View File

@@ -27,9 +27,14 @@ RowLayout {
required property var author
/**
* @brief The timestamp of the event as a NeoChatDateTime.
* @brief The timestamp of the message.
*/
required property NeoChatDateTime dateTime
required property var time
/**
* @brief The timestamp of the message as a string.
*/
required property string timeString
Layout.fillWidth: true
Layout.maximumWidth: Message.maxContentWidth
@@ -78,11 +83,11 @@ RowLayout {
}
QQC2.Label {
id: timeLabel
text: root.dateTime.hourMinuteString
text: root.timeString
horizontalAlignment: Text.AlignRight
color: Kirigami.Theme.disabledTextColor
QQC2.ToolTip.visible: timeHoverHandler.hovered
QQC2.ToolTip.text: root.dateTime.shortDateTime
QQC2.ToolTip.text: root.time.toLocaleString(Qt.locale(), Locale.ShortFormat)
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
HoverHandler {

View File

@@ -16,7 +16,6 @@
#include "contentprovider.h"
#include "eventhandler.h"
#include "models/reactionmodel.h"
#include "neochatdatetime.h"
#include "neochatroom.h"
#include "texthandler.h"
@@ -124,13 +123,22 @@ void EventMessageContentModel::initializeModel()
resetModel();
}
NeoChatDateTime EventMessageContentModel::dateTime() const
QDateTime EventMessageContentModel::time() const
{
const auto event = m_room->getEvent(m_eventId);
if (event.first == nullptr) {
return MessageContentModel::dateTime();
return MessageContentModel::time();
};
return EventHandler::dateTime(m_room, event.first, m_currentState == Pending);
return EventHandler::time(m_room, event.first, m_currentState == Pending);
}
QString EventMessageContentModel::timeString() const
{
const auto event = m_room->getEvent(m_eventId);
if (event.first == nullptr) {
return MessageContentModel::timeString();
};
return EventHandler::timeString(m_room, event.first, u"hh:mm"_s, m_currentState == Pending);
}
QString EventMessageContentModel::authorId() const
@@ -246,7 +254,12 @@ void EventMessageContentModel::resetModel()
return;
}
m_components += MessageComponent{MessageComponentType::Author, {}, {}};
m_components += MessageComponent{MessageComponentType::Author,
QString(),
{
{u"time"_s, EventHandler::time(m_room, event.first, m_currentState == Pending)},
{u"timeString"_s, EventHandler::timeString(m_room, event.first, u"hh:mm"_s, m_currentState == Pending)},
}};
m_components += messageContentComponents();
endResetModel();

View File

@@ -52,7 +52,8 @@ Q_SIGNALS:
private:
void initializeModel();
NeoChatDateTime dateTime() const override;
QDateTime time() const override;
QString timeString() const override;
QString authorId() const override;
QString threadRootId() const override;

View File

@@ -10,7 +10,6 @@
#include "chatbarcache.h"
#include "contentprovider.h"
#include "neochatconnection.h"
#include "neochatdatetime.h"
#include "texthandler.h"
using namespace Quotient;
@@ -80,11 +79,16 @@ QString MessageContentModel::eventId() const
return m_eventId;
}
NeoChatDateTime MessageContentModel::dateTime() const
QDateTime MessageContentModel::time() const
{
return QDateTime::currentDateTime();
}
QString MessageContentModel::timeString() const
{
return time().toLocalTime().toString(u"hh:mm"_s);
}
QString MessageContentModel::authorId() const
{
return m_room->localMember().id();
@@ -132,8 +136,11 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
if (role == EventIdRole) {
return eventId();
}
if (role == DateTimeRole) {
return QVariant::fromValue(dateTime());
if (role == TimeRole) {
return time();
}
if (role == TimeStringRole) {
return timeString();
}
if (role == AuthorRole) {
return QVariant::fromValue<NeochatRoomMember *>(author());
@@ -192,7 +199,8 @@ QHash<int, QByteArray> MessageContentModel::roleNamesStatic()
roles[MessageContentModel::ComponentTypeRole] = "componentType";
roles[MessageContentModel::ComponentAttributesRole] = "componentAttributes";
roles[MessageContentModel::EventIdRole] = "eventId";
roles[MessageContentModel::DateTimeRole] = "dateTime";
roles[MessageContentModel::TimeRole] = "time";
roles[MessageContentModel::TimeStringRole] = "timeString";
roles[MessageContentModel::AuthorRole] = "author";
roles[MessageContentModel::FileTransferInfoRole] = "fileTransferInfo";
roles[MessageContentModel::ItineraryModelRole] = "itineraryModel";

View File

@@ -21,8 +21,6 @@
#include "neochatroom.h"
#include "neochatroommember.h"
class NeoChatDateTime;
/**
* @class MessageContentModel
*
@@ -49,7 +47,8 @@ public:
ComponentTypeRole = Qt::UserRole, /**< The type of component to visualise the message. */
ComponentAttributesRole, /**< The attributes of the component. */
EventIdRole, /**< The matrix event ID of the event. */
DateTimeRole, /**< The timestamp for when the event was sent (as a NeoChatDateTime). */
TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */
TimeStringRole, /**< The timestamp for when the event was sent as a string (in QLocale::ShortFormat). */
AuthorRole, /**< The author of the event. */
FileTransferInfoRole, /**< FileTransferInfo for any downloading files. */
ItineraryModelRole, /**< The itinerary model for a file. */
@@ -126,11 +125,18 @@ protected:
QString m_eventId;
/**
* @brief NeoChatDateTime for the message.
* @brief QDateTime for the message.
*
* The default implementation returns the current time.
*/
virtual NeoChatDateTime dateTime() const;
virtual QDateTime time() const;
/**
* @brief Time for the message as a string in the from "hh:mm".
*
* The default implementation returns the current time.
*/
virtual QString timeString() const;
/**
* @brief The author of the message.

View File

@@ -50,9 +50,9 @@ MessageDelegateBase {
required property MessageContentModel contentModel
/**
* @brief The timestamp of the event as a NeoChatDateTime.
* @brief The date of the event as a string.
*/
required property NeoChatDateTime dateTime
required property string section
/**
* @brief A model with the first 5 other user read markers for this message.
@@ -203,7 +203,7 @@ MessageDelegateBase {
sectionComponent: Kirigami.ListSectionHeader {
horizontalPadding: 0
text: root.dateTime.relativeDate
text: root.section
}
readMarkerComponent: AvatarFlow {

View File

@@ -49,9 +49,9 @@ TimelineDelegate {
required property bool showSection
/**
* @brief The timestamp of the event as a NeoChatDateTime.
* @brief The date of the event as a string.
*/
required property NeoChatDateTime dateTime
required property string section
/**
* @brief A model with the first 5 other user read markers for this message.
@@ -80,7 +80,7 @@ TimelineDelegate {
Layout.fillWidth: true
visible: root.showSection
horizontalPadding: 0
text: root.dateTime.relativeDate
text: root.section
}
RowLayout {
Layout.fillWidth: true

View File

@@ -7,7 +7,6 @@
#include <Quotient/room.h>
#include "messagefiltermodel.h"
#include "neochatdatetime.h"
#include "timelinemessagemodel.h"
using namespace Qt::StringLiterals;
@@ -35,9 +34,8 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
// We need to catch this one and return true if the next media object was
// on a different day.
if (role == TimelineMessageModel::ShowSectionRole) {
const auto day = mapToSource(index).data(TimelineMessageModel::DateTimeRole).value<NeoChatDateTime>().dateTime().toLocalTime().date();
const auto previousEventDay =
mapToSource(this->index(index.row() + 1, 0)).data(TimelineMessageModel::DateTimeRole).value<NeoChatDateTime>().dateTime().toLocalTime().date();
const auto day = mapToSource(index).data(TimelineMessageModel::TimeRole).toDateTime().toLocalTime().date();
const auto previousEventDay = mapToSource(this->index(index.row() + 1, 0)).data(TimelineMessageModel::TimeRole).toDateTime().toLocalTime().date();
return day != previousEventDay;
}

View File

@@ -9,7 +9,6 @@
#include "enums/delegatetype.h"
#include "messagemodel.h"
#include "models/timelinemodel.h"
#include "neochatdatetime.h"
using namespace Quotient;
@@ -180,13 +179,9 @@ bool MessageFilterModel::showAuthor(QModelIndex index) const
if (data(i, TimelineMessageModel::SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
return data(i, TimelineMessageModel::AuthorRole) != data(index, TimelineMessageModel::AuthorRole)
|| data(i, TimelineMessageModel::DelegateTypeRole) == DelegateType::State
|| data(i, TimelineMessageModel::DateTimeRole)
.value<NeoChatDateTime>()
.dateTime()
.msecsTo(data(index, TimelineMessageModel::DateTimeRole).value<NeoChatDateTime>().dateTime())
> 600000
|| data(i, TimelineMessageModel::DateTimeRole).value<NeoChatDateTime>().dateTime().toLocalTime().date().day()
!= data(index, TimelineMessageModel::DateTimeRole).value<NeoChatDateTime>().dateTime().toLocalTime().date().day();
|| data(i, TimelineMessageModel::TimeRole).toDateTime().msecsTo(data(index, TimelineMessageModel::TimeRole).toDateTime()) > 600000
|| data(i, TimelineMessageModel::TimeRole).toDateTime().toLocalTime().date().day()
!= data(index, TimelineMessageModel::TimeRole).toDateTime().toLocalTime().date().day();
}
}

View File

@@ -17,9 +17,8 @@
#include "enums/messagecomponenttype.h"
#include "eventhandler.h"
#include "events/pollevent.h"
#include "models/eventmessagecontentmodel.h"
#include "models/reactionmodel.h"
#include "neochatdatetime.h"
#include "models/eventmessagecontentmodel.h"
#include "neochatroommember.h"
using namespace Quotient;
@@ -111,8 +110,11 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
switch (role) {
case DelegateTypeRole:
return DelegateType::ReadMarker;
case DateTimeRole:
return data(index(m_lastReadEventIndex.row() + 1, 0), DateTimeRole);
case TimeRole: {
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
static const KFormat format;
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
}
case SpecialMarksRole:
// Check if all the earlier events in the timeline are hidden. If so hide this.
for (auto r = row - 1; r >= 0; --r) {
@@ -228,8 +230,12 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
return {};
}
if (role == DateTimeRole) {
return QVariant::fromValue(EventHandler::dateTime(eventRoom, &event.value().get(), isPending));
if (role == TimeRole) {
return EventHandler::time(eventRoom, &event.value().get(), isPending);
}
if (role == SectionRole) {
return EventHandler::timeString(eventRoom, &event.value().get(), true, QLocale::ShortFormat, isPending);
}
if (role == IsThreadedRole) {
@@ -261,8 +267,8 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
// While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
// See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
const auto day = data(idx, DateTimeRole).value<NeoChatDateTime>().dateTime().toLocalTime().date().dayOfYear();
const auto previousEventDay = data(i, DateTimeRole).value<NeoChatDateTime>().dateTime().toLocalTime().date().dayOfYear();
const auto day = data(idx, TimeRole).toDateTime().toLocalTime().date().dayOfYear();
const auto previousEventDay = data(i, TimeRole).toDateTime().toLocalTime().date().dayOfYear();
return day != previousEventDay;
}
}
@@ -336,7 +342,8 @@ QHash<int, QByteArray> MessageModel::roleNames() const
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[DelegateTypeRole] = "delegateType";
roles[EventIdRole] = "eventId";
roles[DateTimeRole] = "dateTime";
roles[TimeRole] = "time";
roles[SectionRole] = "section";
roles[AuthorRole] = "author";
roles[HighlightRole] = "isHighlighted";
roles[SpecialMarksRole] = "marks";

View File

@@ -60,7 +60,8 @@ public:
enum EventRoles {
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
EventIdRole, /**< The matrix event ID of the event. */
DateTimeRole, /**< The timestamp for when the event was sent (as a NeoChatDateTime). */
TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */
SectionRole, /**< The date of the event as a string. */
AuthorRole, /**< The author of the event. */
HighlightRole, /**< Whether the event should be highlighted. */
SpecialMarksRole, /**< Whether the event is hidden or not. */