Mark unread rooms as such in the room list

fixes #313
This commit is contained in:
Konstantinos Sideris 2018-09-13 19:15:58 +03:00
parent 9f3de8679d
commit 8767ea181d
11 changed files with 127 additions and 18 deletions

View File

@ -825,8 +825,10 @@ Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Recei
}
void
Cache::notifyForReadReceipts(lmdb::txn &txn, const std::string &room_id)
Cache::notifyForReadReceipts(const std::string &room_id)
{
auto txn = lmdb::txn::begin(env_);
QSettings settings;
auto local_user = settings.value("auth/user_id").toString();
@ -839,6 +841,47 @@ Cache::notifyForReadReceipts(lmdb::txn &txn, const std::string &room_id)
if (!matches.empty())
emit newReadReceipts(QString::fromStdString(room_id), matches);
txn.commit();
}
void
Cache::calculateRoomReadStatus()
{
const auto joined_rooms = joinedRooms();
std::map<QString, bool> readStatus;
for (const auto &room : joined_rooms)
readStatus.emplace(QString::fromStdString(room), calculateRoomReadStatus(room));
emit roomReadStatus(readStatus);
}
bool
Cache::calculateRoomReadStatus(const std::string &room_id)
{
auto txn = lmdb::txn::begin(env_);
// Get last event id on the room.
const auto last_event_id = getLastMessageInfo(txn, room_id).event_id;
const auto localUser = utils::localUser().toStdString();
txn.commit();
// Retrieve all read receipts for that event.
const auto receipts = readReceipts(last_event_id, QString::fromStdString(room_id));
if (receipts.size() == 0)
return true;
// Check if the local user has a read receipt for it.
for (auto it = receipts.cbegin(); it != receipts.cend(); it++) {
if (it->second == localUser)
return false;
}
return true;
}
void
@ -880,11 +923,15 @@ Cache::saveState(const mtx::responses::Sync &res)
txn.commit();
std::map<QString, bool> readStatus;
for (const auto &room : res.rooms.join) {
auto tmpTxn = lmdb::txn::begin(env_);
notifyForReadReceipts(tmpTxn, room.first);
tmpTxn.commit();
notifyForReadReceipts(room.first);
readStatus.emplace(QString::fromStdString(room.first),
calculateRoomReadStatus(room.first));
}
emit roomReadStatus(readStatus);
}
void

View File

@ -89,6 +89,7 @@ from_json(const json &j, ReadReceiptKey &key)
struct DescInfo
{
QString event_id;
QString username;
QString userid;
QString body;
@ -356,7 +357,7 @@ public:
void removePendingReceipt(lmdb::txn &txn,
const std::string &room_id,
const std::string &event_id);
void notifyForReadReceipts(lmdb::txn &txn, const std::string &room_id);
void notifyForReadReceipts(const std::string &room_id);
std::vector<QString> pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
QByteArray image(const QString &url) const;
@ -376,6 +377,11 @@ public:
return getRoomInfo(roomsWithStateUpdates(sync));
}
//! Calculates which the read status of a room.
//! Whether all the events in the timeline have been read.
bool calculateRoomReadStatus(const std::string &room_id);
void calculateRoomReadStatus();
QVector<SearchResult> searchUsers(const std::string &room_id,
const std::string &query,
std::uint8_t max_items = 5);
@ -444,6 +450,7 @@ public:
signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status);
private:
//! Save an invited room.

View File

@ -677,6 +677,9 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
view_manager_,
&TimelineViewManager::updateReadReceipts);
connect(
cache::client(), &Cache::roomReadStatus, room_list_, &RoomList::updateReadStatus);
const bool isInitialized = cache::client()->isInitialized();
const bool isValid = cache::client()->isFormatValid();
@ -794,6 +797,8 @@ ChatPage::loadStateFromCache()
emit initializeEmptyViews(cache::client()->roomMessages());
emit initializeRoomList(cache::client()->roomInfo());
cache::client()->calculateRoomReadStatus();
} catch (const mtx::crypto::olm_exception &e) {
nhlog::crypto()->critical("failed to restore olm account: {}", e.what());
emit dropToLoginPageCb(

View File

@ -31,9 +31,11 @@
constexpr int MaxUnreadCountDisplayed = 99;
constexpr int Padding = 9;
constexpr int IconSize = 44;
constexpr int MaxHeight = IconSize + 2 * Padding;
constexpr int Padding = 9;
constexpr int UnreadLineWidth = 4;
constexpr int UnreadLineOffset = 4;
constexpr int IconSize = 44;
constexpr int MaxHeight = IconSize + 2 * Padding;
constexpr int InviteBtnX = IconSize + 2 * Padding;
constexpr int InviteBtnY = IconSize / 2 + Padding + Padding / 3;
@ -89,6 +91,8 @@ RoomInfoListItem::RoomInfoListItem(QString room_id, RoomInfo info, QWidget *pare
{
init(parent);
QString emptyEventId;
// HACK
// We use fake message info with an old date to pin
// the invite events to the top.
@ -96,7 +100,8 @@ RoomInfoListItem::RoomInfoListItem(QString room_id, RoomInfo info, QWidget *pare
// State events in invited rooms don't contain timestamp info,
// so we can't use them for sorting.
if (roomType_ == RoomType::Invited)
lastMsgInfo_ = {"-", "-", "-", "-", QDateTime::currentDateTime().addYears(10)};
lastMsgInfo_ = {
emptyEventId, "-", "-", "-", "-", QDateTime::currentDateTime().addYears(10)};
}
void
@ -305,6 +310,15 @@ RoomInfoListItem::paintEvent(QPaintEvent *event)
p.setBrush(Qt::NoBrush);
p.drawText(r.translated(0, -0.5), Qt::AlignCenter, countTxt);
}
if (!isPressed_ && hasUnreadMessages_) {
QPen pen;
pen.setWidth(UnreadLineWidth);
pen.setColor(highlightedBackgroundColor_);
p.setPen(pen);
p.drawLine(0, UnreadLineOffset, 0, height() - UnreadLineOffset);
}
}
void

View File

@ -121,6 +121,13 @@ public:
}
bool isInvite() { return roomType_ == RoomType::Invited; }
void setReadState(bool hasUnreadMessages)
{
if (hasUnreadMessages_ != hasUnreadMessages) {
hasUnreadMessages_ = hasUnreadMessages;
update();
}
}
signals:
void clicked(const QString &room_id);
@ -164,7 +171,8 @@ private:
Menu *menu_;
QAction *leaveRoom_;
bool isPressed_ = false;
bool isPressed_ = false;
bool hasUnreadMessages_ = true;
int unreadMsgCount_ = 0;

View File

@ -455,3 +455,16 @@ RoomList::firstRoom() const
return std::pair<QString, QSharedPointer<RoomInfoListItem>>(firstRoom->first,
firstRoom->second);
}
void
RoomList::updateReadStatus(const std::map<QString, bool> &status)
{
for (const auto &room : status) {
if (roomExists(room.first)) {
auto item = rooms_.at(room.first);
if (item)
item->setReadState(room.second);
}
}
}

View File

@ -72,6 +72,7 @@ public slots:
void updateUnreadMessageCount(const QString &roomid, int count);
void updateRoomDescription(const QString &roomid, const DescInfo &info);
void closeJoinRoomDialog(bool isJoining, QString roomAlias);
void updateReadStatus(const std::map<QString, bool> &status);
protected:
void paintEvent(QPaintEvent *event) override;

View File

@ -126,6 +126,7 @@ utils::getMessageDescription(const TimelineEvent &event,
info.userid = sender;
info.body = QString(" %1").arg(messageDescription<Encrypted>());
info.timestamp = utils::descriptiveTime(ts);
info.event_id = QString::fromStdString(msg.event_id);
info.datetime = ts;
return info;

View File

@ -109,6 +109,7 @@ createDescriptionInfo(const Event &event, const QString &localUser, const QStrin
bool isEmote = std::is_same<T, Emote>::value;
return DescInfo{
QString::fromStdString(msg.event_id),
isEmote ? "" : (sender == localUser ? "You" : username),
sender,
(isText || isEmote)

View File

@ -317,16 +317,23 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty,
if (formatted_body == body.trimmed().toHtmlEscaped())
formatted_body = body.toHtmlEscaped();
QString emptyEventId;
if (ty == mtx::events::MessageType::Emote) {
formatted_body = QString("<em>%1</em>").arg(formatted_body);
descriptionMsg_ = {"",
descriptionMsg_ = {emptyEventId,
"",
userid,
QString("* %1 %2").arg(displayName).arg(body),
utils::descriptiveTime(timestamp),
timestamp};
} else {
descriptionMsg_ = {
"You: ", userid, body, utils::descriptiveTime(timestamp), timestamp};
descriptionMsg_ = {emptyEventId,
"You: ",
userid,
body,
utils::descriptiveTime(timestamp),
timestamp};
}
formatted_body = utils::linkifyMessage(formatted_body);
@ -496,7 +503,8 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
auto formatted_body = utils::linkifyMessage(utils::getMessageBody(event).trimmed());
auto body = QString::fromStdString(event.content.body).trimmed().toHtmlEscaped();
descriptionMsg_ = {Cache::displayName(room_id_, sender),
descriptionMsg_ = {event_id_,
Cache::displayName(room_id_, sender),
sender,
" sent a notification",
utils::descriptiveTime(timestamp),
@ -545,7 +553,8 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
auto displayName = Cache::displayName(room_id_, sender);
formatted_body = QString("<em>%1</em>").arg(formatted_body);
descriptionMsg_ = {"",
descriptionMsg_ = {event_id_,
"",
sender,
QString("* %1 %2").arg(displayName).arg(body),
utils::descriptiveTime(timestamp),
@ -592,7 +601,8 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text>
auto displayName = Cache::displayName(room_id_, sender);
QSettings settings;
descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName,
descriptionMsg_ = {event_id_,
sender == settings.value("auth/user_id") ? "You" : displayName,
sender,
QString(": %1").arg(body),
utils::descriptiveTime(timestamp),

View File

@ -319,7 +319,8 @@ TimelineItem::setupLocalWidgetLayout(Widget *widget, const QString &userid, bool
auto displayName = Cache::displayName(room_id_, userid);
auto timestamp = QDateTime::currentDateTime();
descriptionMsg_ = {"You",
descriptionMsg_ = {"", // No event_id up until this point.
"You",
userid,
QString(" %1").arg(utils::messageDescription<Widget>()),
utils::descriptiveTime(timestamp),
@ -358,7 +359,8 @@ TimelineItem::setupWidgetLayout(Widget *widget, const Event &event, bool withSen
auto displayName = Cache::displayName(room_id_, sender);
QSettings settings;
descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName,
descriptionMsg_ = {event_id_,
sender == settings.value("auth/user_id") ? "You" : displayName,
sender,
QString(" %1").arg(utils::messageDescription<Widget>()),
utils::descriptiveTime(timestamp),