Cache refactoring

pull/1/head
Konstantinos Sideris 7 years ago
parent ca66940ec3
commit 2f00fc51bf
  1. 25
      include/AvatarProvider.h
  2. 317
      include/Cache.h
  3. 60
      include/ChatPage.h
  4. 2
      include/MatrixClient.h
  5. 42
      include/RoomInfoListItem.h
  6. 19
      include/RoomList.h
  7. 10
      include/SuggestionsPopup.hpp
  8. 7
      include/TextInputWidget.h
  9. 2
      include/Utils.h
  10. 5
      include/dialogs/ReadReceipts.h
  11. 44
      include/timeline/TimelineItem.h
  12. 9
      include/timeline/TimelineView.h
  13. 5
      include/timeline/TimelineViewManager.h
  14. 49
      src/AvatarProvider.cc
  15. 893
      src/Cache.cc
  16. 442
      src/ChatPage.cc
  17. 24
      src/MatrixClient.cc
  18. 63
      src/RoomInfoListItem.cc
  19. 77
      src/RoomList.cc
  20. 15
      src/SuggestionsPopup.cpp
  21. 49
      src/TextInputWidget.cc
  22. 20
      src/Utils.cc
  23. 22
      src/dialogs/ReadReceipts.cc
  24. 52
      src/timeline/TimelineItem.cc
  25. 5
      src/timeline/TimelineView.cc
  26. 23
      src/timeline/TimelineViewManager.cc

@ -17,45 +17,30 @@
#pragma once
#include <QHash>
#include <QImage>
#include <QSharedPointer>
#include <QUrl>
#include <functional>
class MatrixClient;
class TimelineItem;
//! Saved cache data per user.
struct AvatarData
{
//! The avatar image of the user.
QImage img;
//! The url that was used to download the avatar.
QUrl url;
};
class AvatarProvider : public QObject
{
Q_OBJECT
public:
static void init(QSharedPointer<MatrixClient> client);
static void init(QSharedPointer<MatrixClient> client) { client_ = client; }
//! The callback is called with the downloaded avatar for the given user
//! or the avatar is downloaded first and then saved for re-use.
static void resolve(const QString &userId,
static void resolve(const QString &room_id,
const QString &userId,
QObject *receiver,
std::function<void(QImage)> callback);
//! Used to initialize the mapping user -> avatar url.
static void setAvatarUrl(const QString &userId, const QUrl &url);
//! Remove all saved data.
static void clear() { avatars_.clear(); };
private:
//! Update the cache with the downloaded avatar.
static void updateAvatar(const QString &uid, const QImage &img);
static QSharedPointer<MatrixClient> client_;
using UserID = QString;
static std::map<UserID, AvatarData> avatars_;
static QHash<QString, QImage> avatars_;
};

@ -17,12 +17,23 @@
#pragma once
#include <QDebug>
#include <QDir>
#include <json.hpp>
#include <lmdb++.h>
#include <mtx/responses.hpp>
#include "RoomState.h"
#include "Utils.h"
struct SearchResult
{
QString user_id;
QString display_name;
};
Q_DECLARE_METATYPE(SearchResult)
Q_DECLARE_METATYPE(QVector<SearchResult>)
//! Used to uniquely identify a list of read receipts.
struct ReadReceiptKey
@ -44,6 +55,60 @@ from_json(const json &j, ReadReceiptKey &key)
key.room_id = j.at("room_id").get<std::string>();
}
//! UI info associated with a room.
struct RoomInfo
{
//! The calculated name of the room.
std::string name;
//! The topic of the room.
std::string topic;
//! The calculated avatar url of the room.
std::string avatar_url;
//! Whether or not the room is an invite.
bool is_invite = false;
};
inline void
to_json(json &j, const RoomInfo &info)
{
j["name"] = info.name;
j["topic"] = info.topic;
j["avatar_url"] = info.avatar_url;
j["is_invite"] = info.is_invite;
}
inline void
from_json(const json &j, RoomInfo &info)
{
info.name = j.at("name");
info.topic = j.at("topic");
info.avatar_url = j.at("avatar_url");
info.is_invite = j.at("is_invite");
}
//! Basic information per member;
struct MemberInfo
{
std::string name;
std::string avatar_url;
};
inline void
to_json(json &j, const MemberInfo &info)
{
j["name"] = info.name;
j["avatar_url"] = info.avatar_url;
}
inline void
from_json(const json &j, MemberInfo &info)
{
info.name = j.at("name");
info.avatar_url = j.at("avatar_url");
}
Q_DECLARE_METATYPE(RoomInfo)
class Cache : public QObject
{
Q_OBJECT
@ -51,22 +116,50 @@ class Cache : public QObject
public:
Cache(const QString &userId, QObject *parent = nullptr);
void setState(const QString &nextBatchToken,
const std::map<QString, QSharedPointer<RoomState>> &states);
static QHash<QString, QString> DisplayNames;
static QHash<QString, QString> AvatarUrls;
static std::string displayName(const std::string &room_id, const std::string &user_id);
static QString displayName(const QString &room_id, const QString &user_id);
static QString avatarUrl(const QString &room_id, const QString &user_id);
static void removeDisplayName(const QString &room_id, const QString &user_id);
static void removeAvatarUrl(const QString &room_id, const QString &user_id);
static void insertDisplayName(const QString &room_id,
const QString &user_id,
const QString &display_name);
static void insertAvatarUrl(const QString &room_id,
const QString &user_id,
const QString &avatar_url);
//! Load saved data for the display names & avatars.
void populateMembers();
std::vector<std::string> joinedRooms();
QMap<QString, RoomInfo> roomInfo();
//! Calculate & return the name of the room.
QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
//! Retrieve the topic of the room if any.
QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
//! Retrieve the room avatar's url if any.
QString getRoomAvatarUrl(lmdb::txn &txn,
lmdb::dbi &statesdb,
lmdb::dbi &membersdb,
const QString &room_id);
void saveState(const mtx::responses::Sync &res);
bool isInitialized() const;
QString nextBatchToken() const;
void states();
using Invites = std::map<std::string, mtx::responses::InvitedRoom>;
Invites invites();
void setInvites(const Invites &invites);
void deleteData();
void unmount() { isMounted_ = false; };
void removeRoom(const QString &roomid);
void removeInvite(const QString &roomid);
void removeInvite(const std::string &room_id);
void removeRoom(lmdb::txn &txn, const std::string &roomid);
void removeRoom(const std::string &roomid);
void removeRoom(const QString &roomid) { removeRoom(roomid.toStdString()); };
void setup();
bool isFormatValid();
@ -88,24 +181,206 @@ public:
QByteArray image(const QString &url) const;
void saveImage(const QString &url, const QByteArray &data);
signals:
void statesLoaded(std::map<QString, RoomState> states);
std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &rooms);
std::map<QString, RoomInfo> roomUpdates(const mtx::responses::Sync &sync)
{
return getRoomInfo(roomsWithStateUpdates(sync));
}
QVector<SearchResult> getAutocompleteMatches(const std::string &room_id,
const std::string &query,
std::uint8_t max_items = 5);
private:
//! Save an invited room.
void saveInvite(lmdb::txn &txn,
lmdb::dbi &statesdb,
lmdb::dbi &membersdb,
const mtx::responses::InvitedRoom &room);
QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
//! Remove a room from the cache.
// void removeLeftRoom(lmdb::txn &txn, const std::string &room_id);
template<class T>
void saveStateEvents(lmdb::txn &txn,
const lmdb::dbi &statesdb,
const lmdb::dbi &membersdb,
const std::string &room_id,
const std::vector<T> &events)
{
for (const auto &e : events)
saveStateEvent(txn, statesdb, membersdb, room_id, e);
}
template<class T>
void saveStateEvent(lmdb::txn &txn,
const lmdb::dbi &statesdb,
const lmdb::dbi &membersdb,
const std::string &room_id,
const T &event)
{
using namespace mtx::events;
using namespace mtx::events::state;
if (mpark::holds_alternative<StateEvent<Member>>(event)) {
const auto e = mpark::get<StateEvent<Member>>(event);
switch (e.content.membership) {
//
// We only keep users with invite or join membership.
//
case Membership::Invite:
case Membership::Join: {
auto display_name = e.content.display_name.empty()
? e.state_key
: e.content.display_name;
// Lightweight representation of a member.
MemberInfo tmp{display_name, e.content.avatar_url};
lmdb::dbi_put(txn,
membersdb,
lmdb::val(e.state_key),
lmdb::val(json(tmp).dump()));
insertDisplayName(QString::fromStdString(room_id),
QString::fromStdString(e.state_key),
QString::fromStdString(display_name));
insertAvatarUrl(QString::fromStdString(room_id),
QString::fromStdString(e.state_key),
QString::fromStdString(e.content.avatar_url));
break;
}
default: {
lmdb::dbi_del(
txn, membersdb, lmdb::val(e.state_key), lmdb::val(""));
removeDisplayName(QString::fromStdString(room_id),
QString::fromStdString(e.state_key));
removeAvatarUrl(QString::fromStdString(room_id),
QString::fromStdString(e.state_key));
break;
}
}
return;
}
if (!isStateEvent(event))
return;
mpark::visit(
[&txn, &statesdb](auto e) {
lmdb::dbi_put(
txn, statesdb, lmdb::val(to_string(e.type)), lmdb::val(json(e).dump()));
},
event);
}
template<class T>
bool isStateEvent(const T &e)
{
using namespace mtx::events;
using namespace mtx::events::state;
return mpark::holds_alternative<StateEvent<Aliases>>(e) ||
mpark::holds_alternative<StateEvent<state::Avatar>>(e) ||
mpark::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
mpark::holds_alternative<StateEvent<Create>>(e) ||
mpark::holds_alternative<StateEvent<GuestAccess>>(e) ||
mpark::holds_alternative<StateEvent<HistoryVisibility>>(e) ||
mpark::holds_alternative<StateEvent<JoinRules>>(e) ||
mpark::holds_alternative<StateEvent<Name>>(e) ||
mpark::holds_alternative<StateEvent<PowerLevels>>(e) ||
mpark::holds_alternative<StateEvent<Topic>>(e);
}
template<class T>
bool containsStateUpdates(const T &e)
{
using namespace mtx::events;
using namespace mtx::events::state;
return mpark::holds_alternative<StateEvent<state::Avatar>>(e) ||
mpark::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
mpark::holds_alternative<StateEvent<Name>>(e) ||
mpark::holds_alternative<StateEvent<Member>>(e) ||
mpark::holds_alternative<StateEvent<Topic>>(e);
}
bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e)
{
using namespace mtx::events;
using namespace mtx::events::state;
return mpark::holds_alternative<StrippedEvent<state::Avatar>>(e) ||
mpark::holds_alternative<StrippedEvent<CanonicalAlias>>(e) ||
mpark::holds_alternative<StrippedEvent<Name>>(e) ||
mpark::holds_alternative<StrippedEvent<Member>>(e) ||
mpark::holds_alternative<StrippedEvent<Topic>>(e);
}
void saveInvites(lmdb::txn &txn,
const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
//! Remove any saved invites that are not found in the input.
void removeStaleInvites(lmdb::txn &txn, const std::map<std::string, bool> &curr);
//! Sends signals for the rooms that are removed.
void removeLeftRooms(lmdb::txn &txn,
const std::map<std::string, mtx::responses::LeftRoom> &rooms)
{
for (const auto &room : rooms)
removeRoom(txn, room.first);
}
lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE);
}
lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE);
}
lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
}
lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
}
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event)
{
if (!event.content.display_name.empty())
return QString::fromStdString(event.content.display_name);
return QString::fromStdString(event.state_key);
}
void setNextBatchToken(lmdb::txn &txn, const std::string &token);
void setNextBatchToken(lmdb::txn &txn, const QString &token);
void insertRoomState(lmdb::txn &txn,
const QString &roomid,
const QSharedPointer<RoomState> &state);
lmdb::env env_;
lmdb::dbi stateDb_;
lmdb::dbi roomDb_;
lmdb::dbi syncStateDb_;
lmdb::dbi roomsDb_;
lmdb::dbi invitesDb_;
lmdb::dbi imagesDb_;
lmdb::dbi mediaDb_;
lmdb::dbi readReceiptsDb_;
bool isMounted_;
QString userId_;
QString localUserId_;
QString cacheDirectory_;
};

@ -24,16 +24,16 @@
#include <QTimer>
#include <QWidget>
#include "Cache.h"
#include "CommunitiesList.h"
#include "Community.h"
#include <mtx.hpp>
class Cache;
class MatrixClient;
class OverlayModal;
class QuickSwitcher;
class RoomList;
class RoomSettings;
class RoomState;
class SideBarActions;
class Splitter;
@ -52,6 +52,9 @@ constexpr int CONSENSUS_TIMEOUT = 1000;
constexpr int SHOW_CONTENT_TIMEOUT = 3000;
constexpr int TYPING_REFRESH_TIMEOUT = 10000;
Q_DECLARE_METATYPE(mtx::responses::Rooms);
Q_DECLARE_METATYPE(std::vector<std::string>);
class ChatPage : public QWidget
{
Q_OBJECT
@ -88,6 +91,14 @@ signals:
void showLoginPage(const QString &msg);
void showUserSettingsPage();
void showOverlayProgressBar();
void startConsesusTimer();
void initializeRoomList(QMap<QString, RoomInfo>);
void initializeViews(const mtx::responses::Rooms &rooms);
void initializeEmptyViews(const std::vector<std::string> &rooms);
void syncUI(const mtx::responses::Rooms &rooms);
void continueSync(const QString &next_batch);
void syncRoomlist(const std::map<QString, RoomInfo> &updates);
private slots:
void showUnreadMessageNotification(int count);
@ -98,9 +109,9 @@ private slots:
void syncCompleted(const mtx::responses::Sync &response);
void changeTopRoomInfo(const QString &room_id);
void logout();
void addRoom(const QString &room_id);
void removeRoom(const QString &room_id);
void removeInvite(const QString &room_id);
//! Handles initial sync failures.
void retryInitialSync(int status_code = -1);
private:
static ChatPage *instance_;
@ -110,28 +121,11 @@ private:
using Membership = mtx::events::StateEvent<mtx::events::state::Member>;
using Memberships = std::map<std::string, Membership>;
using JoinedRooms = std::map<std::string, mtx::responses::JoinedRoom>;
using LeftRooms = std::map<std::string, mtx::responses::LeftRoom>;
using InvitedRooms = std::map<std::string, mtx::responses::InvitedRoom>;
using LeftRooms = std::map<std::string, mtx::responses::LeftRoom>;
void removeLeftRooms(const LeftRooms &rooms);
void updateJoinedRooms(const JoinedRooms &rooms);
void trackInvites(const InvitedRooms &rooms)
{
for (const auto &invite : rooms)
roomInvites_[QString::fromStdString(invite.first)] = true;
}
std::map<QString, QSharedPointer<RoomState>> generateMembershipDifference(
const JoinedRooms &rooms,
const RoomStates &states) const;
void updateTypingUsers(const QString &roomid, const std::vector<std::string> &user_ids);
using MemberEvent = mtx::events::StateEvent<mtx::events::state::Member>;
void updateUserDisplayName(const MemberEvent &event);
void updateUserAvatarUrl(const MemberEvent &event);
void loadStateFromCache();
void deleteConfigs();
void resetUI();
@ -141,10 +135,6 @@ private:
template<class Collection>
Memberships getMemberships(const std::vector<Collection> &events) const;
template<class Collection>
void updateUserMetadata(const std::vector<Collection> &collection);
void retryInitialSync(int status_code = -1);
//! Update the room with the new notification count.
void updateRoomNotificationCount(const QString &room_id, uint16_t notification_count);
@ -186,8 +176,6 @@ private:
UserInfoWidget *user_info_widget_;
RoomStates roomStates_;
std::map<QString, QSharedPointer<RoomSettings>> roomSettings_;
std::map<QString, bool> roomInvites_;
std::map<QString, QSharedPointer<Community>> communities_;
@ -211,22 +199,6 @@ private:
QSharedPointer<Cache> cache_;
};
template<class Collection>
void
ChatPage::updateUserMetadata(const std::vector<Collection> &collection)
{
using Member = mtx::events::StateEvent<mtx::events::state::Member>;
for (const auto &event : collection) {
if (mpark::holds_alternative<Member>(event)) {
auto member = mpark::get<Member>(event);
updateUserAvatarUrl(member);
updateUserDisplayName(member);
}
}
}
template<class Collection>
std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>>
ChatPage::getMemberships(const std::vector<Collection> &collection) const

@ -32,6 +32,8 @@ signals:
void avatarDownloaded(const QImage &img);
};
Q_DECLARE_METATYPE(mtx::responses::Sync)
/*
* MatrixClient provides the high level API to communicate with
* a Matrix homeserver. All the responses are returned through signals.

@ -24,11 +24,9 @@
#include <mtx/responses.hpp>
#include "RoomState.h"
class Menu;
class RippleOverlay;
class RoomSettings;
struct RoomInfo;
struct DescInfo
{
@ -70,24 +68,13 @@ class RoomInfoListItem : public QWidget
Q_PROPERTY(QColor btnTextColor READ btnTextColor WRITE setBtnTextColor)
public:
RoomInfoListItem(QSharedPointer<RoomSettings> settings,
QSharedPointer<RoomState> state,
QString room_id,
QWidget *parent = 0);
RoomInfoListItem(QString room_id, mtx::responses::InvitedRoom room, QWidget *parent = 0);
RoomInfoListItem(QString room_id, RoomInfo info, QWidget *parent = 0);
void updateUnreadMessageCount(int count);
void clearUnreadMessageCount() { updateUnreadMessageCount(0); };
void setState(QSharedPointer<RoomState> state)
{
state_ = state;
update();
}
QString roomId() { return roomId_; }
bool isPressed() const { return isPressed_; }
QSharedPointer<RoomState> state() const { return state_; }
int unreadMessageCount() const { return unreadMsgCount_; }
void setAvatar(const QImage &avatar_image);
@ -133,6 +120,15 @@ public:
void setBubbleFgColor(QColor &color) { bubbleFgColor_ = color; }
void setBubbleBgColor(QColor &color) { bubbleBgColor_ = color; }
void setRoomName(const QString &name) { roomName_ = name; }
void setRoomType(bool isInvite)
{
if (isInvite)
roomType_ = RoomType::Invited;
else
roomType_ = RoomType::Joined;
}
signals:
void clicked(const QString &room_id);
void leaveRoom(const QString &room_id);
@ -150,15 +146,7 @@ protected:
private:
void init(QWidget *parent);
QString roomName()
{
if (roomType_ == RoomType::Joined)
return state_->getName();
return roomName_;
}
QString notificationText();
QString roomName() { return roomName_; }
RippleOverlay *ripple_overlay_;
@ -170,9 +158,6 @@ private:
RoomType roomType_ = RoomType::Joined;
// State information for the joined rooms.
QSharedPointer<RoomState> state_;
// State information for the invited rooms.
mtx::responses::InvitedRoom invitedRoom_;
@ -184,11 +169,8 @@ private:
QPixmap roomAvatar_;
Menu *menu_;
QAction *toggleNotifications_;
QAction *leaveRoom_;
QSharedPointer<RoomSettings> roomSettings_;
bool isPressed_ = false;
int unreadMsgCount_ = 0;

@ -23,14 +23,13 @@
#include <QVBoxLayout>
#include <QWidget>
#include "Cache.h"
#include <mtx.hpp>
class LeaveRoomDialog;
class MatrixClient;
class Cache;
class OverlayModal;
class RoomInfoListItem;
class RoomSettings;
class RoomState;
class Sync;
class UserSettings;
@ -46,22 +45,18 @@ public:
QWidget *parent = 0);
void setCache(QSharedPointer<Cache> cache) { cache_ = cache; }
void setInitialRooms(const std::map<QString, QSharedPointer<RoomSettings>> &settings,
const std::map<QString, QSharedPointer<RoomState>> &states);
void sync(const std::map<QString, QSharedPointer<RoomState>> &states,
const std::map<QString, QSharedPointer<RoomSettings>> &settings);
void syncInvites(const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
void initialize(const QMap<QString, RoomInfo> &info);
void sync(const std::map<QString, RoomInfo> &info);
void clear();
void clear() { rooms_.clear(); };
void updateAvatar(const QString &room_id, const QString &url);
void addRoom(const QSharedPointer<RoomSettings> &settings,
const QSharedPointer<RoomState> &state,
const QString &room_id);
void addInvitedRoom(const QString &room_id, const mtx::responses::InvitedRoom &room);
void addRoom(const QString &room_id, const RoomInfo &info);
void addInvitedRoom(const QString &room_id, const RoomInfo &info);
void removeRoom(const QString &room_id, bool reset);
void setFilterRooms(bool filterRooms);
void setRoomFilter(std::vector<QString> room_ids);
void updateRoom(const QString &room_id, const RoomInfo &info);
signals:
void roomChanged(const QString &room_id);

@ -6,15 +6,7 @@
#include <QWidget>
class Avatar;
struct SearchResult
{
QString user_id;
QString display_name;
};
Q_DECLARE_METATYPE(SearchResult)
Q_DECLARE_METATYPE(QVector<SearchResult>)
struct SearchResult;
class PopupItem : public QWidget
{

@ -36,7 +36,7 @@
#include "emoji/PickButton.h"
class RoomState;
class Cache;
namespace dialogs {
class PreviewUploadOverlay;
@ -131,12 +131,12 @@ public:
QColor borderColor() const { return borderColor_; }
void setBorderColor(QColor &color) { borderColor_ = color; }
void setCache(QSharedPointer<Cache> cache) { cache_ = cache; }
public slots:
void openFileSelection();
void hideUploadSpinner();
void focusLineEdit() { input_->setFocus(); }
void setRoomState(QSharedPointer<RoomState> state) { currState_ = state; }
private slots:
void addSelectedEmoji(const QString &emoji);
@ -172,8 +172,7 @@ private:
FlatButton *sendMessageBtn_;
emoji::PickButton *emojiBtn_;
//! State of the current room.
QSharedPointer<RoomState> currState_;
QSharedPointer<Cache> cache_;
QColor borderColor_;
};

@ -16,7 +16,7 @@ descriptiveTime(const QDateTime &then);
//! Generate a message description from the event to be displayed
//! in the RoomList.
DescInfo
getMessageDescription(const TimelineEvent &event, const QString &localUser);
getMessageDescription(const TimelineEvent &event, const QString &localUser, const QString &room_id);
//! Get the first character of a string, taking into account that
//! surrogate pairs might be in use.

@ -16,7 +16,10 @@ class ReceiptItem : public QWidget
Q_OBJECT
public:
ReceiptItem(QWidget *parent, const QString &user_id, uint64_t timestamp);
ReceiptItem(QWidget *parent,
const QString &user_id,
uint64_t timestamp,
const QString &room_id);
private:
QString dateFormat(const QDateTime &then) const;

@ -26,9 +26,9 @@
#include <QStyleOption>
#include "AvatarProvider.h"
#include "Cache.h"
#include "ChatPage.h"
#include "RoomInfoListItem.h"
#include "TimelineViewManager.h"
#include "Utils.h"
class ImageItem;
@ -43,12 +43,15 @@ class TimelineItem : public QWidget
public:
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &e,
bool with_sender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> &e,
bool with_sender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> &e,
bool with_sender,
const QString &room_id,
QWidget *parent = 0);
// For local messages.
@ -57,28 +60,49 @@ public:
const QString &userid,
QString body,
bool withSender,
const QString &room_id,
QWidget *parent = 0);
// m.image
TimelineItem(ImageItem *item, const QString &userid, bool withSender, QWidget *parent = 0);
TimelineItem(FileItem *item, const QString &userid, bool withSender, QWidget *parent = 0);
TimelineItem(AudioItem *item, const QString &userid, bool withSender, QWidget *parent = 0);
TimelineItem(VideoItem *item, const QString &userid, bool withSender, QWidget *parent = 0);
TimelineItem(ImageItem *item,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(FileItem *item,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(AudioItem *item,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(VideoItem *item,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent = 0);
TimelineItem(ImageItem *img,
const mtx::events::RoomEvent<mtx::events::msg::Image> &e,
bool with_sender,
const QString &room_id,
QWidget *parent);
TimelineItem(FileItem *file,
const mtx::events::RoomEvent<mtx::events::msg::File> &e,
bool with_sender,
const QString &room_id,
QWidget *parent);
TimelineItem(AudioItem *audio,
const mtx::events::RoomEvent<mtx::events::msg::Audio> &e,
bool with_sender,
const QString &room_id,
QWidget *parent);
TimelineItem(VideoItem *video,
const mtx::events::RoomEvent<mtx::events::msg::Video> &e,
bool with_sender,
const QString &room_id,
QWidget *parent);
void setUserAvatar(const QImage &pixmap);
@ -86,7 +110,7 @@ public:
QString eventId() const { return event_id_; }
void setEventId(const QString &event_id) { event_id_ = event_id; }
void markReceived();
void setRoomId(const QString &room_id) { room_id_ = room_id; }
void setRoomId(QString room_id) { room_id_ = room_id; }
void sendReadReceipt() const
{
if (!event_id_.isEmpty())
@ -159,7 +183,7 @@ TimelineItem::setupLocalWidgetLayout(Widget *widget,
const QString &msgDescription,
bool withSender)
{
auto displayName = TimelineViewManager::displayName(userid);
auto displayName = Cache::displayName(room_id_, userid);
auto timestamp = QDateTime::currentDateTime();
descriptionMsg_ = {"You",
@ -183,7 +207,7 @@ TimelineItem::setupLocalWidgetLayout(Widget *widget,
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
userid, this, [this](const QImage &img) { setUserAvatar(img); });
room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
setupSimpleLayout();
@ -208,7 +232,7 @@ TimelineItem::setupWidgetLayout(Widget *widget,
const auto sender = QString::fromStdString(event.sender);
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
auto displayName = TimelineViewManager::displayName(sender);
auto displayName = Cache::displayName(room_id_, sender);
QSettings settings;
descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName,
@ -232,7 +256,7 @@ TimelineItem::setupWidgetLayout(Widget *widget,
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
sender, this, [this](const QImage &img) { setUserAvatar(img); });
room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
setupSimpleLayout();

@ -259,8 +259,7 @@ TimelineView::addUserMessage(const QString &url,
auto widget = new Widget(client_, url, trimmed, size, this);
TimelineItem *view_item =
new TimelineItem(widget, local_user_, with_sender, scroll_widget_);
view_item->setRoomId(room_id_);
new TimelineItem(widget, local_user_, with_sender, room_id_, scroll_widget_);
addTimelineItem(view_item);
@ -280,8 +279,7 @@ template<class Event>
TimelineItem *
TimelineView::createTimelineItem(const Event &event, bool withSender)
{
TimelineItem *item = new TimelineItem(event, withSender, scroll_widget_);
item->setRoomId(room_id_);
TimelineItem *item = new TimelineItem(event, withSender, room_id_, scroll_widget_);
return item;
}
@ -290,8 +288,7 @@ TimelineItem *
TimelineView::createTimelineItem(const Event &event, bool withSender)
{
auto eventWidget = new Widget(client_, event);
auto item = new TimelineItem(eventWidget, event, withSender, scroll_widget_);
item->setRoomId(room_id_);
auto item = new TimelineItem(eventWidget, event, withSender, room_id_, scroll_widget_);
return item;
}

@ -39,7 +39,7 @@ public:
// Initialize with timeline events.
void initialize(const mtx::responses::Rooms &rooms);
// Empty initialization.
void initialize(const std::vector<QString> &rooms);
void initialize(const std::vector<std::string> &rooms);
void addRoom(const mtx::responses::JoinedRoom &room, const QString &room_id);
void addRoom(const QString &room_id);
@ -51,9 +51,6 @@ public:
bool hasLoaded() const;
static QString chooseRandomColor();
static QString displayName(const QString &userid);
static std::map<QString, QString> DISPLAY_NAMES;
signals:
void clearRoomMessageCount(QString roomid);

@ -16,41 +16,33 @@
*/
#include "AvatarProvider.h"
#include "Cache.h"
#include "MatrixClient.h"
QSharedPointer<MatrixClient> AvatarProvider::client_;
std::map<QString, AvatarData> AvatarProvider::avatars_;
void
AvatarProvider::init(QSharedPointer<MatrixClient> client)
{
client_ = client;
}
void
AvatarProvider::updateAvatar(const QString &uid, const QImage &img)
{
auto avatarData = &avatars_[uid];
avatarData->img = img;
}
QHash<QString, QImage> AvatarProvider::avatars_;
void
AvatarProvider::resolve(const QString &userId,
AvatarProvider::resolve(const QString &room_id,
const QString &user_id,
QObject *receiver,
std::function<void(QImage)> callback)
{
if (avatars_.find(userId) == avatars_.end())
const auto key = QString("%1 %2").arg(room_id).arg(user_id);
if (!Cache::AvatarUrls.contains(key))
return;
auto img = avatars_[userId].img;
if (avatars_.contains(key)) {
auto img = avatars_[key];
if (!img.isNull()) {
callback(img);
return;
if (!img.isNull()) {
callback(img);
return;
}
}
auto proxy = client_->fetchUserAvatar(avatars_[userId].url);
auto proxy = client_->fetchUserAvatar(Cache::avatarUrl(room_id, user_id));
if (proxy.isNull())
return;
@ -58,18 +50,9 @@ AvatarProvider::resolve(const QString &userId,
connect(proxy.data(),
&DownloadMediaProxy::avatarDownloaded,
receiver,
[userId, proxy, callback](const QImage &img) {
[user_id, proxy, callback, key](const QImage &img) {
proxy->deleteLater();
updateAvatar(userId, img);
avatars_.insert(key, img);
callback(img);
});
}
void
AvatarProvider::setAvatarUrl(const QString &userId, const QUrl &url)
{
AvatarData data;
data.url = url;
avatars_.emplace(userId, data);
}

File diff suppressed because it is too large Load Diff

@ -28,7 +28,6 @@
#include "OverlayModal.h"
#include "QuickSwitcher.h"
#include "RoomList.h"
#include "RoomSettings.h"
#include "RoomState.h"
#include "SideBarActions.h"
#include "Splitter.h"
@ -158,20 +157,21 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
typingDisplay_->setUsers(users);
});
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::stopTyping);
connect(room_list_, &RoomList::roomChanged, text_input_, [this](const QString &room_id) {
if (roomStates_.find(room_id) != roomStates_.end())
text_input_->setRoomState(roomStates_[room_id]);
else
qWarning() << "no state found for room_id" << room_id;
});
connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo);
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit);
connect(
room_list_, &RoomList::roomChanged, view_manager_, &TimelineViewManager::setHistoryView);
connect(room_list_, &RoomList::acceptInvite, client_.data(), &MatrixClient::joinRoom);
connect(room_list_, &RoomList::declineInvite, client_.data(), &MatrixClient::leaveRoom);
connect(room_list_, &RoomList::acceptInvite, this, [this](const QString &room_id) {
view_manager_->addRoom(room_id);
client_->joinRoom(room_id);
room_list_->removeRoom(room_id, currentRoom() == room_id);
});
connect(room_list_, &RoomList::declineInvite, this, [this](const QString &room_id) {
client_->leaveRoom(room_id);
room_list_->removeRoom(room_id, currentRoom() == room_id);
});
connect(text_input_, &TextInputWidget::startedTyping, this, [this]() {
if (!userSettings_->isTypingNotificationsEnabled())
@ -329,15 +329,22 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
connect(client_.data(), &MatrixClient::joinedRoom, this, [this](const QString &room_id) {
emit showNotification("You joined the room.");
removeInvite(room_id);
// We remove any invites with the same room_id.
try {
cache_->removeInvite(room_id.toStdString());
} catch (const lmdb::error &e) {
emit showNotification(QString("Failed to remove invite: %1")
.arg(QString::fromStdString(e.what())));
}
});
connect(client_.data(), &MatrixClient::leftRoom, this, &ChatPage::removeRoom);
connect(client_.data(), &MatrixClient::invitedUser, this, [this](QString, QString user) {
emit showNotification(QString("Invited user %1").arg(user));
});
connect(client_.data(), &MatrixClient::roomCreated, this, [this](QString room_id) {
emit showNotification(QString("Room %1 created").arg(room_id));
});
connect(client_.data(), &MatrixClient::leftRoom, this, &ChatPage::removeRoom);
connect(client_.data(), &MatrixClient::redactionFailed, this, [this](const QString &error) {
emit showNotification(QString("Message redaction failed: %1").arg(error));
});
@ -394,7 +401,38 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
AvatarProvider::init(client);
connect(this, &ChatPage::continueSync, this, [this](const QString &next_batch) {
syncTimeoutTimer_->start(SYNC_RETRY_TIMEOUT);
client_->setNextBatchToken(next_batch);
client_->sync();
});
connect(this, &ChatPage::startConsesusTimer, this, [this]() {
consensusTimer_->start(CONSENSUS_TIMEOUT);
showContentTimer_->start(SHOW_CONTENT_TIMEOUT);
});
connect(this, &ChatPage::initializeRoomList, room_list_, &RoomList::initialize);
connect(this,
&ChatPage::initializeViews,
view_manager_,
[this](const mtx::responses::Rooms &rooms) { view_manager_->initialize(rooms); });
connect(
this,
&ChatPage::initializeEmptyViews,
this,
[this](const std::vector<std::string> &rooms) { view_manager_->initialize(rooms); });
connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) {
view_manager_->initialize(rooms);
removeLeftRooms(rooms.leave);
});
connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync);
instance_ = this;
qRegisterMetaType<std::map<QString, RoomInfo>>();
qRegisterMetaType<QMap<QString, RoomInfo>>();
qRegisterMetaType<mtx::responses::Rooms>();
qRegisterMetaType<std::vector<std::string>>();
}
void
@ -412,8 +450,6 @@ ChatPage::resetUI()
{
roomAvatars_.clear();
room_list_->clear();
roomSettings_.clear();
roomStates_.clear();
top_bar_->reset();
user_info_widget_->reset();
view_manager_->clearAll();
@ -451,6 +487,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
cache_ = QSharedPointer<Cache>(new Cache(userid));
room_list_->setCache(cache_);
text_input_->setCache(cache_);
try {
cache_->setup();
@ -467,7 +504,6 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
}
} catch (const lmdb::error &e) {
qCritical() << "Cache failure" << e.what();
cache_->unmount();
cache_->deleteData();
qInfo() << "Falling back to initial sync ...";
}
@ -482,25 +518,28 @@ ChatPage::syncCompleted(const mtx::responses::Sync &response)
{
syncTimeoutTimer_->stop();
updateJoinedRooms(response.rooms.join);
removeLeftRooms(response.rooms.leave);
// Process ephemeral data per room.
for (const auto &room : response.rooms.join) {
auto room_id = QString::fromStdString(room.first);
const auto nextBatchToken = QString::fromStdString(response.next_batch);
auto stateDiff = generateMembershipDifference(response.rooms.join, roomStates_);
QtConcurrent::run(cache_.data(), &Cache::setState, nextBatchToken, stateDiff);
QtConcurrent::run(cache_.data(), &Cache::setInvites, response.rooms.invite);
room_list_->sync(roomStates_, roomSettings_);
room_list_->syncInvites(response.rooms.invite);
trackInvites(response.rooms.invite);
view_manager_->sync(response.rooms);
updateTypingUsers(room_id, room.second.ephemeral.typing);
updateRoomNotificationCount(room_id,
room.second.unread_notifications.notification_count);
}
client_->setNextBatchToken(nextBatchToken);
client_->sync();
QtConcurrent::run([this, res = std::move(response)]() {
try {
cache_->saveState(res);
emit syncRoomlist(cache_->roomUpdates(res));
} catch (const lmdb::error &e) {
std::cout << "save cache error:" << e.what() << '\n';
// TODO: retry sync.
return;
}
syncTimeoutTimer_->start(SYNC_RETRY_TIMEOUT);
emit syncUI(std::move(res.rooms));
emit continueSync(cache_->nextBatchToken());
});
}
void
@ -508,57 +547,22 @@ ChatPage::initialSyncCompleted(const mtx::responses::Sync &response)
{
initialSyncTimer_->stop();
auto joined = response.rooms.join;
for (auto it = joined.cbegin(); it != joined.cend(); ++it) {
auto roomState = QSharedPointer<RoomState>(new RoomState);
qDebug() << "initial sync completed";
// Build the current state from the timeline and state events.
roomState->updateFromEvents(it->second.state.events);
roomState->updateFromEvents(it->second.timeline.events);
// Remove redundant memberships.
roomState->removeLeaveMemberships();
// Resolve room name and avatar. e.g in case of one-to-one chats.
roomState->resolveName();
roomState->resolveAvatar();
const auto room_id = QString::fromStdString(it->first);
roomStates_.emplace(room_id, roomState);
roomSettings_.emplace(room_id,
QSharedPointer<RoomSettings>(new RoomSettings(room_id)));
for (const auto &membership : roomState->memberships) {
updateUserDisplayName(membership.second);
updateUserAvatarUrl(membership.second);
QtConcurrent::run([this, res = std::move(response)]() {
try {
cache_->saveState(res);
emit initializeRoomList(cache_->roomInfo());
} catch (const lmdb::error &e) {
qWarning() << "cache error:" << QString::fromStdString(e.what());
emit retryInitialSync();
return;
}
QApplication::processEvents();
}
QtConcurrent::run(cache_.data(),
&Cache::setState,
QString::fromStdString(response.next_batch),
roomStates_);
QtConcurrent::run(cache_.data(), &Cache::setInvites, response.rooms.invite);
// Create timelines
view_manager_->initialize(response.rooms);
// Initialize room list.
room_list_->setInitialRooms(roomSettings_, roomStates_);
room_list_->syncInvites(response.rooms.invite);
trackInvites(response.rooms.invite);
client_->setNextBatchToken(QString::fromStdString(response.next_batch));
client_->sync();
// Add messages
view_manager_->sync(response.rooms);
emit contentLoaded();
emit initializeViews(std::move(res.rooms));
emit continueSync(cache_->nextBatchToken());
emit contentLoaded();
});
}
void
@ -613,19 +617,26 @@ ChatPage::updateOwnCommunitiesInfo(const QList<QString> &own_communities)
void
ChatPage::changeTopRoomInfo(const QString &room_id)
{
if (roomStates_.find(room_id) == roomStates_.end())
return;
try {
auto room_info = cache_->getRoomInfo({room_id.toStdString()});
auto state = roomStates_[room_id];
if (room_info.find(room_id) == room_info.end())
return;
top_bar_->updateRoomName(state->getName());
top_bar_->updateRoomTopic(state->getTopic());
top_bar_->setRoomSettings(roomSettings_[room_id]);
const auto name = QString::fromStdString(room_info[room_id].name);
const auto avatar_url = QString::fromStdString(room_info[room_id].avatar_url);
if (roomAvatars_.find(room_id) != roomAvatars_.end())
top_bar_->updateRoomAvatar(roomAvatars_[room_id].toImage());
else
top_bar_->updateRoomAvatarFromName(state->getName());
top_bar_->updateRoomName(name);
top_bar_->updateRoomTopic(QString::fromStdString(room_info[room_id].topic));
if (roomAvatars_.find(room_id) != roomAvatars_.end())
top_bar_->updateRoomAvatar(roomAvatars_[room_id].toImage());
else
top_bar_->updateRoomAvatarFromName(name);
} catch (const lmdb::error &e) {
qWarning() << "failed to change top bar room info"
<< QString::fromStdString(e.what());
}
current_room_ = room_id;
}
@ -645,64 +656,26 @@ ChatPage::showUnreadMessageNotification(int count)
void
ChatPage::loadStateFromCache()
{
qDebug() << "Restoring state from cache";
qDebug() << "Restored nextBatchToken" << cache_->nextBatchToken();
client_->setNextBatchToken(cache_->nextBatchToken());
qRegisterMetaType<std::map<QString, RoomState>>();
QtConcurrent::run(cache_.data(), &Cache::states);
connect(
cache_.data(), &Cache::statesLoaded, this, [this](std::map<QString, RoomState> rooms) {
qDebug() << "Cache data loaded";
std::vector<QString> roomKeys;
for (auto const &room : rooms) {
auto roomState = QSharedPointer<RoomState>(new RoomState(room.second));
// Clean up and prepare state for use.
roomState->removeLeaveMemberships();
roomState->resolveName();
roomState->resolveAvatar();
// Save the current room state.
roomStates_.emplace(room.first, roomState);
// Create or restore the settings for this room.
roomSettings_.emplace(
room.first, QSharedPointer<RoomSettings>(new RoomSettings(room.first)));
// Resolve user avatars.
for (auto const &membership : roomState->memberships) {
updateUserDisplayName(membership.second);
updateUserAvatarUrl(membership.second);
}
roomKeys.emplace_back(room.first);
}
qDebug() << "restoring state from cache";
// Initializing empty timelines.
view_manager_->initialize(roomKeys);
QtConcurrent::run([this]() {
try {
cache_->populateMembers();
// Initialize room list from the restored state and settings.
room_list_->setInitialRooms(roomSettings_, roomStates_);
const auto invites = cache_->invites();
room_list_->syncInvites(invites);
trackInvites(invites);
// Check periodically if the timelines have been loaded.
consensusTimer_->start(CONSENSUS_TIMEOUT);
emit initializeRoomList(cache_->roomInfo());
emit initializeEmptyViews(cache_->joinedRooms());
} catch (const lmdb::error &e) {
std::cout << "load cache error:" << e.what() << '\n';
// TODO Clear cache and restart.
return;
}
// Show the content if consensus can't be achieved.
showContentTimer_->start(SHOW_CONTENT_TIMEOUT);
// Start receiving events.
emit continueSync(cache_->nextBatchToken());
// Start receiving events.
client_->sync();
});
// Check periodically if the timelines have been loaded.
emit startConsesusTimer();
});
}
void
@ -734,69 +707,30 @@ ChatPage::showQuickSwitcher()
std::map<QString, QString> rooms;
for (auto const &state : roomStates_) {
QString deambiguator =
QString::fromStdString(state.second->canonical_alias.content.alias);
if (deambiguator == "")
deambiguator = state.first;
rooms.emplace(state.second->getName() + " (" + deambiguator + ")", state.first);
}
quickSwitcher_->setRoomList(rooms);
quickSwitcherModal_->show();
}
void
ChatPage::addRoom(const QString &room_id)
{
if (roomStates_.find(room_id) == roomStates_.end()) {
auto room_state = QSharedPointer<RoomState>(new RoomState);
roomStates_.emplace(room_id, room_state);
roomSettings_.emplace(room_id,
QSharedPointer<RoomSettings>(new RoomSettings(room_id)));
room_list_->addRoom(roomSettings_[room_id], roomStates_[room_id], room_id);
room_list_->highlightSelectedRoom(room_id);
changeTopRoomInfo(room_id);
}
}
void
ChatPage::removeRoom(const QString &room_id)
{
roomStates_.erase(room_id);
roomSettings_.erase(room_id);
try {
cache_->removeRoom(room_id);
cache_->removeInvite(room_id);
auto info = cache_->roomInfo();
for (auto it = info.begin(); it != info.end(); ++it)
rooms.emplace(QString::fromStdString(it.value().name), it.key());
quickSwitcher_->setRoomList(rooms);
quickSwitcherModal_->show();
} catch (const lmdb::error &e) {
qCritical() << "The cache couldn't be updated: " << e.what();
// TODO: Notify the user.
cache_->unmount();
cache_->deleteData();
const auto err = QString::fromStdString(e.what());
emit showNotification(QString("Failed to load room list: %1").arg(err));
}
room_list_->removeRoom(room_id, room_id == current_room_);
roomInvites_.erase(room_id);
}
void
ChatPage::removeInvite(const QString &room_id)
ChatPage::removeRoom(const QString &room_id)
{
try {
cache_->removeInvite(room_id);
cache_->removeRoom(room_id);
cache_->removeInvite(room_id.toStdString());
} catch (const lmdb::error &e) {
qCritical() << "The cache couldn't be updated: " << e.what();
// TODO: Notify the user.
cache_->unmount();
cache_->deleteData();
}
room_list_->removeRoom(room_id, room_id == current_room_);
roomInvites_.erase(room_id);
}
void
@ -821,7 +755,7 @@ ChatPage::updateTypingUsers(const QString &roomid, const std::vector<std::string
if (user == user_id)
continue;
users.append(TimelineViewManager::displayName(user));
users.append(Cache::displayName(roomid, user));
}
users.sort();
@ -833,133 +767,13 @@ ChatPage::updateTypingUsers(const QString &roomid, const std::vector<std::string
typingUsers_.emplace(roomid, users);
}
void
ChatPage::updateUserAvatarUrl(const mtx::events::StateEvent<mtx::events::state::Member> &membership)
{
auto uid = QString::fromStdString(membership.state_key);
auto url = QString::fromStdString(membership.content.avatar_url);
if (!url.isEmpty())
AvatarProvider::setAvatarUrl(uid, url);
}
void
ChatPage::updateUserDisplayName(
const mtx::events::StateEvent<mtx::events::state::Member> &membership)
{
auto displayName = QString::fromStdString(membership.content.display_name);
auto stateKey = QString::fromStdString(membership.state_key);
if (!displayName.isEmpty())
TimelineViewManager::DISPLAY_NAMES.emplace(stateKey, displayName);
}
void
ChatPage::removeLeftRooms(const std::map<std::string, mtx::responses::LeftRoom> &rooms)
{
for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
const auto room_id = QString::fromStdString(it->first);
if (roomStates_.find(room_id) != roomStates_.end())
removeRoom(room_id);
if (roomInvites_.find(room_id) != roomInvites_.end())
removeInvite(room_id);
}
}
void
ChatPage::updateJoinedRooms(const std::map<std::string, mtx::responses::JoinedRoom> &rooms)
{
for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
const auto roomid = QString::fromStdString(it->first);
if (roomInvites_.find(roomid) != roomInvites_.end())
removeInvite(roomid);
updateTypingUsers(roomid, it->second.ephemeral.typing);
updateRoomNotificationCount(roomid,
it->second.unread_notifications.notification_count);
if (it->second.ephemeral.receipts.size() > 0)
QtConcurrent::run(cache_.data(),
&Cache::updateReadReceipt,
it->first,
it->second.ephemeral.receipts);
const auto newStateEvents = it->second.state;
const auto newTimelineEvents = it->second.timeline;
// Merge the new updates for rooms that we are tracking.
if (roomStates_.find(roomid) != roomStates_.end()) {
auto oldState = roomStates_[roomid];
oldState->updateFromEvents(newStateEvents.events);
oldState->updateFromEvents(newTimelineEvents.events);
oldState->resolveName();
oldState->resolveAvatar();
} else {
// Build the current state from the timeline and state events.
auto roomState = QSharedPointer<RoomState>(new RoomState);
roomState->updateFromEvents(newStateEvents.events);
roomState->updateFromEvents(newTimelineEvents.events);
// Resolve room name and avatar. e.g in case of one-to-one chats.
roomState->resolveName();
roomState->resolveAvatar();
roomStates_.emplace(roomid, roomState);
roomSettings_.emplace(
roomid, QSharedPointer<RoomSettings>(new RoomSettings(roomid)));
view_manager_->addRoom(it->second, roomid);
}
updateUserMetadata(newStateEvents.events);
updateUserMetadata(newTimelineEvents.events);
if (roomid == current_room_)
changeTopRoomInfo(roomid);
QApplication::processEvents();
}
}
std::map<QString, QSharedPointer<RoomState>>
ChatPage::generateMembershipDifference(
const std::map<std::string, mtx::responses::JoinedRoom> &rooms,
const std::map<QString, QSharedPointer<RoomState>> &states) const
{
std::map<QString, QSharedPointer<RoomState>> stateDiff;
for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
const auto room_id = QString::fromStdString(it->first);
if (states.find(room_id) == states.end())
continue;
auto all_memberships = getMemberships(it->second.state.events);
auto timelineMemberships = getMemberships(it->second.timeline.events);
// We have to process first the state events and then the timeline.
for (auto mm = timelineMemberships.cbegin(); mm != timelineMemberships.cend(); ++mm)
all_memberships.emplace(mm->first, mm->second);
auto local = QSharedPointer<RoomState>(new RoomState);
local->aliases = states.at(room_id)->aliases;
local->avatar = states.at(room_id)->avatar;
local->canonical_alias = states.at(room_id)->canonical_alias;
local->history_visibility = states.at(room_id)->history_visibility;
local->join_rules = states.at(room_id)->join_rules;
local->name = states.at(room_id)->name;
local->power_levels = states.at(room_id)->power_levels;
local->topic = states.at(room_id)->topic;
local->memberships = all_memberships;
stateDiff.emplace(room_id, local);
room_list_->removeRoom(room_id, room_id == current_room_);
}
return stateDiff;
}
void

@ -28,6 +28,7 @@
#include <QProcessEnvironment>
#include <QSettings>
#include <QUrlQuery>
#include <QtConcurrent>
#include <mtx/errors.hpp>
#include "Deserializable.h"
@ -438,16 +439,15 @@ MatrixClient::initialSync() noexcept
return;
}
auto data = reply->readAll();
try {
mtx::responses::Sync response = nlohmann::json::parse(data);
emit initialSyncCompleted(response);
} catch (std::exception &e) {
qWarning() << "Initial sync error:" << e.what();
emit initialSyncFailed();
return;
}
qRegisterMetaType<mtx::responses::Sync>();
QtConcurrent::run([data = reply->readAll(), this]() {
try {
emit initialSyncCompleted(nlohmann::json::parse(std::move(data)));
} catch (std::exception &e) {
qWarning() << "Initial sync error:" << e.what();
emit initialSyncFailed();
}
});
});
}
@ -730,10 +730,8 @@ MatrixClient::fetchUserAvatar(const QUrl &avatarUrl)
{
QList<QString> url_parts = avatarUrl.toString().split("mxc://");
if (url_parts.size() != 2) {
qDebug() << "Invalid format for user avatar:" << avatarUrl.toString();
if (url_parts.size() != 2)
return QSharedPointer<DownloadMediaProxy>();
}
QUrlQuery query;
query.addQueryItem("width", "128");

@ -22,12 +22,12 @@
#include <variant.hpp>
#include "Cache.h"
#include "Config.h"
#include "Menu.h"
#include "Ripple.h"
#include "RippleOverlay.h"
#include "RoomInfoListItem.h"
#include "RoomSettings.h"
#include "Theme.h"
#include "Utils.h"
@ -73,15 +73,20 @@ RoomInfoListItem::init(QWidget *parent)
headingFont_ = font_;
headingFont_.setPixelSize(conf::roomlist::fonts::heading);
headingFont_.setWeight(60);
menu_ = new Menu(this);
leaveRoom_ = new QAction(tr("Leave room"), this);
connect(leaveRoom_, &QAction::triggered, this, [this]() { emit leaveRoom(roomId_); });
menu_->addAction(leaveRoom_);
}
RoomInfoListItem::RoomInfoListItem(QString room_id,
mtx::responses::InvitedRoom room,
QWidget *parent)
RoomInfoListItem::RoomInfoListItem(QString room_id, RoomInfo info, QWidget *parent)
: QWidget(parent)
, roomType_{RoomType::Invited}
, invitedRoom_{std::move(room)}
, roomId_{std::move(room_id)}
, roomType_{info.is_invite ? RoomType::Invited : RoomType::Joined}
, roomId_(std::move(room_id))
, roomName_{QString::fromStdString(std::move(info.name))}
, isPressed_(false)
, unreadMsgCount_(0)
{
init(parent);
@ -91,47 +96,8 @@ RoomInfoListItem::RoomInfoListItem(QString room_id,
//
// State events in invited rooms don't contain timestamp info,
// so we can't use them for sorting.
auto now = QDateTime::currentDateTime();
lastMsgInfo_ = {"-", "-", "-", "-", now.addYears(10)};
roomAvatar_ = QString::fromStdString(invitedRoom_.avatar());
roomName_ = QString::fromStdString(invitedRoom_.name());
}
RoomInfoListItem::RoomInfoListItem(QSharedPointer<RoomSettings> settings,
QSharedPointer<RoomState> state,
QString room_id,
QWidget *parent)
: QWidget(parent)
, state_(state)
, roomId_(room_id)
, roomSettings_{settings}
, isPressed_(false)
, unreadMsgCount_(0)
{
init(parent);
menu_ = new Menu(this);
toggleNotifications_ = new QAction(notificationText(), this);
connect(toggleNotifications_, &QAction::triggered, this, [this]() {
roomSettings_->toggleNotifications();
});
leaveRoom_ = new QAction(tr("Leave room"), this);
connect(leaveRoom_, &QAction::triggered, this, [this]() { emit leaveRoom(roomId_); });
menu_->addAction(toggleNotifications_);
menu_->addAction(leaveRoom_);
}
QString
RoomInfoListItem::notificationText()
{
if (roomSettings_.isNull() || roomSettings_->isNotificationsEnabled())
return QString(tr("Disable notifications"));
return tr("Enable notifications");
if (roomType_ == RoomType::Invited)
lastMsgInfo_ = {"-", "-", "-", "-", QDateTime::currentDateTime().addYears(10)};
}
void
@ -352,7 +318,6 @@ RoomInfoListItem::contextMenuEvent(QContextMenuEvent *event)
if (roomType_ == RoomType::Invited)
return;
toggleNotifications_->setText(notificationText());
menu_->popup(event->globalPos());
}

@ -26,7 +26,6 @@
#include "OverlayModal.h"
#include "RoomInfoListItem.h"
#include "RoomList.h"
#include "RoomSettings.h"
#include "RoomState.h"
#include "UserSettingsPage.h"
@ -74,17 +73,11 @@ RoomList::RoomList(QSharedPointer<MatrixClient> client,
}
void
RoomList::clear()
RoomList::addRoom(const QString &room_id, const RoomInfo &info)
{
rooms_.clear();
}
auto room_item = new RoomInfoListItem(room_id, info, scrollArea_);
room_item->setRoomName(QString::fromStdString(std::move(info.name)));
void
RoomList::addRoom(const QSharedPointer<RoomSettings> &settings,
const QSharedPointer<RoomState> &state,
const QString &room_id)
{
auto room_item = new RoomInfoListItem(settings, state, room_id, scrollArea_);
connect(room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom);
connect(room_item, &RoomInfoListItem::leaveRoom, this, [](const QString &room_id) {
MainWindow::instance()->openLeaveRoomDialog(room_id);
@ -92,8 +85,8 @@ RoomList::addRoom(const QSharedPointer<RoomSettings> &settings,
rooms_.emplace(room_id, QSharedPointer<RoomInfoListItem>(room_item));
if (!state->getAvatar().toString().isEmpty())
updateAvatar(room_id, state->getAvatar().toString());
if (!info.avatar_url.empty())
updateAvatar(room_id, QString::fromStdString(info.avatar_url));
int pos = contentsLayout_->count() - 1;
contentsLayout_->insertWidget(pos, room_item);
@ -164,20 +157,19 @@ RoomList::calculateUnreadMessageCount()
}
void
RoomList::setInitialRooms(const std::map<QString, QSharedPointer<RoomSettings>> &settings,
const std::map<QString, QSharedPointer<RoomState>> &states)
RoomList::initialize(const QMap<QString, RoomInfo> &info)
{
qDebug() << "initialize room list";
rooms_.clear();
if (settings.size() != states.size()) {
qWarning() << "Initializing room list";
qWarning() << "Different number of room states and room settings";
return;
for (auto it = info.begin(); it != info.end(); it++) {
if (it.value().is_invite)
addInvitedRoom(it.key(), it.value());
else
addRoom(it.key(), it.value());
}
for (auto const &state : states)
addRoom(settings.at(state.first), state.second, state.first);
if (rooms_.empty())
return;
@ -190,21 +182,11 @@ RoomList::setInitialRooms(const std::map<QString, QSharedPointer<RoomSettings>>
}
void
RoomList::sync(const std::map<QString, QSharedPointer<RoomState>> &states,
const std::map<QString, QSharedPointer<RoomSettings>> &settings)
RoomList::sync(const std::map<QString, RoomInfo> &info)
{
for (auto const &state : states) {
if (!roomExists(state.first))
addRoom(settings.at(state.first), state.second, state.first);
auto room = rooms_[state.first];
auto new_avatar = state.second->getAvatar();
updateAvatar(state.first, new_avatar.toString());
room->setState(state.second);
}
for (const auto &room : info)
updateRoom(room.first, room.second);
}
void
@ -368,14 +350,24 @@ RoomList::paintEvent(QPaintEvent *)
}
void
RoomList::syncInvites(const std::map<std::string, mtx::responses::InvitedRoom> &rooms)
RoomList::updateRoom(const QString &room_id, const RoomInfo &info)
{
for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
const auto room_id = QString::fromStdString(it->first);
qDebug() << "updateRoom" << QString::fromStdString(info.name) << room_id;
if (!roomExists(room_id))
addInvitedRoom(room_id, it->second);
if (!roomExists(room_id)) {
if (info.is_invite)
addInvitedRoom(room_id, info);
else
addRoom(room_id, info);
return;
}
auto room = rooms_[room_id];
updateAvatar(room_id, QString::fromStdString(info.avatar_url));
room->setRoomName(QString::fromStdString(info.name));
room->setRoomType(info.is_invite);
room->update();
}
void
@ -386,15 +378,16 @@ RoomList::setRoomFilter(std::vector<QString> room_ids)
}
void
RoomList::addInvitedRoom(const QString &room_id, const mtx::responses::InvitedRoom &room)
RoomList::addInvitedRoom(const QString &room_id, const RoomInfo &info)
{
auto room_item = new RoomInfoListItem(room_id, room, scrollArea_);
auto room_item = new RoomInfoListItem(room_id, info, scrollArea_);
connect(room_item, &RoomInfoListItem::acceptInvite, this, &RoomList::acceptInvite);
connect(room_item, &RoomInfoListItem::declineInvite, this, &RoomList::declineInvite);
rooms_.emplace(room_id, QSharedPointer<RoomInfoListItem>(room_item));
updateAvatar(room_id, QString::fromStdString(room.avatar()));
updateAvatar(room_id, QString::fromStdString(info.avatar_url));
int pos = contentsLayout_->count() - 1;
contentsLayout_->insertWidget(pos, room_item);

@ -1,10 +1,11 @@
#include "Avatar.h"
#include "AvatarProvider.h"
#include "Cache.h"
#include "ChatPage.h"
#include "Config.h"
#include "DropShadow.h"
#include "SuggestionsPopup.hpp"
#include "Utils.h"
#include "timeline/TimelineViewManager.h"
#include <QDebug>
#include <QPaintEvent>
@ -30,7 +31,7 @@ PopupItem::PopupItem(QWidget *parent, const QString &user_id)
QFont font;
font.setPixelSize(conf::popup::font);
auto displayName = TimelineViewManager::displayName(user_id);
auto displayName = Cache::displayName(ChatPage::instance()->currentRoom(), user_id);
avatar_->setSize(conf::popup::avatar);
avatar_->setLetter(utils::firstChar(displayName));
@ -45,8 +46,10 @@ PopupItem::PopupItem(QWidget *parent, const QString &user_id)
topLayout_->addWidget(avatar_);
topLayout_->addWidget(userName_, 1);
AvatarProvider::resolve(
user_id, this, [this](const QImage &img) { avatar_->setImage(img); });
AvatarProvider::resolve(ChatPage::instance()->currentRoom(),
user_id,
this,
[this](const QImage &img) { avatar_->setImage(img); });
}
void
@ -65,7 +68,7 @@ void
PopupItem::mousePressEvent(QMouseEvent *event)
{
if (event->buttons() != Qt::RightButton)
emit clicked(TimelineViewManager::displayName(user_id_));
emit clicked(Cache::displayName(ChatPage::instance()->currentRoom(), user_id_));
QWidget::mousePressEvent(event);
}
@ -164,7 +167,7 @@ SuggestionsPopup::selectHoveredSuggestion()
return;
const auto &widget = qobject_cast<PopupItem *>(item->widget());
emit itemSelected(TimelineViewManager::displayName(widget->user()));
emit itemSelected(Cache::displayName(ChatPage::instance()->currentRoom(), widget->user()));
resetSelection();
}

@ -31,8 +31,9 @@
#include <variant.hpp>
#include "Cache.h"
#include "ChatPage.h"
#include "Config.h"
#include "RoomState.h"
#include "TextInputWidget.h"
#include "Utils.h"
@ -40,7 +41,6 @@ static constexpr size_t INPUT_HISTORY_SIZE = 127;
static constexpr int MAX_TEXTINPUT_HEIGHT = 120;
static constexpr int InputHeight = 26;
static constexpr int ButtonHeight = 24;
static constexpr int MaxPopupItems = 5;
FilteredTextEdit::FilteredTextEdit(QWidget *parent)
: QTextEdit{parent}
@ -454,49 +454,16 @@ TextInputWidget::TextInputWidget(QWidget *parent)
input_->setFixedHeight(textInputHeight);
});
connect(input_, &FilteredTextEdit::showSuggestions, this, [this](const QString &q) {
if (q.isEmpty() || currState_.isNull())
if (q.isEmpty() || cache_.isNull())
return;
QtConcurrent::run([this, q = q.toLower().toStdString()]() {
std::multimap<int, std::pair<std::string, std::string>> items;
auto get_name = [](auto membership) {
auto name = membership.second.content.display_name;
auto key = membership.first;
// Remove the leading '@' character.
if (name.empty()) {
key.erase(0, 1);
name = key;
}
return std::make_pair(key, name);
};
for (const auto &m : currState_->memberships) {
const auto user = get_name(m);
const int score = utils::levenshtein_distance(q, user.second);
items.emplace(score, user);
try {
emit input_->resultsRetrieved(cache_->getAutocompleteMatches(
ChatPage::instance()->currentRoom().toStdString(), q));
} catch (const lmdb::error &e) {
std::cout << e.what() << '\n';
}
QVector<SearchResult> results;
auto end = items.begin();
if (items.size() >= MaxPopupItems)
std::advance(end, MaxPopupItems);
else if (items.size() > 0)
std::advance(end, items.size());
for (auto it = items.begin(); it != end; it++) {
const auto user = it->second;
results.push_back(
SearchResult{QString::fromStdString(user.first),
QString::fromStdString(user.second)});
}
emit input_->resultsRetrieved(results);
});
});

@ -1,5 +1,5 @@
#include "Cache.h"
#include "Utils.h"
#include "timeline/TimelineViewManager.h"
#include <variant.hpp>
@ -22,7 +22,9 @@ utils::descriptiveTime(const QDateTime &then)
}
DescInfo
utils::getMessageDescription(const TimelineEvent &event, const QString &localUser)
utils::getMessageDescription(const TimelineEvent &event,
const QString &localUser,
const QString &room_id)
{
using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>;
using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
@ -36,7 +38,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<Audio>(event);
QString sender = QString::fromStdString(msg.sender);
const auto username = TimelineViewManager::displayName(sender);
const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
return DescInfo{sender == localUser ? "You" : username,
@ -48,7 +50,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
auto msg = mpark::get<Emote>(event);
QString sender = QString::fromStdString(msg.sender);
const auto username = TimelineViewManager::displayName(sender);
const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
const auto body = QString::fromStdString(msg.content.body).trimmed();
@ -61,7 +63,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<File>(event);
QString sender = QString::fromStdString(msg.sender);
const auto username = TimelineViewManager::displayName(sender);
const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
return DescInfo{sender == localUser ? "You" : username,
@ -73,7 +75,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<Image>(event);
QString sender = QString::fromStdString(msg.sender);
const auto username = TimelineViewManager::displayName(sender);
const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
return DescInfo{sender == localUser ? "You" : username,
@ -85,7 +87,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<Notice>(event);
QString sender = QString::fromStdString(msg.sender);
const auto username = TimelineViewManager::displayName(sender);
const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
return DescInfo{
@ -94,7 +96,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<Text>(event);
QString sender = QString::fromStdString(msg.sender);
const auto username = TimelineViewManager::displayName(sender);
const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
const auto body = QString::fromStdString(msg.content.body).trimmed();
@ -107,7 +109,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<Video>(event);
QString sender = QString::fromStdString(msg.sender);
const auto username = TimelineViewManager::displayName(sender);
const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
return DescInfo{sender == localUser ? "You" : username,

@ -6,17 +6,21 @@
#include <QTimer>
#include <QVBoxLayout>
#include "ChatPage.h"
#include "Config.h"
#include "Utils.h"
#include "Avatar.h"
#include "AvatarProvider.h"
#include "Cache.h"
#include "dialogs/ReadReceipts.h"
#include "timeline/TimelineViewManager.h"
using namespace dialogs;
ReceiptItem::ReceiptItem(QWidget *parent, const QString &user_id, uint64_t timestamp)
ReceiptItem::ReceiptItem(QWidget *parent,
const QString &user_id,
uint64_t timestamp,
const QString &room_id)
: QWidget(parent)
{
topLayout_ = new QHBoxLayout(this);
@ -29,7 +33,7 @@ ReceiptItem::ReceiptItem(QWidget *parent, const QString &user_id, uint64_t times
QFont font;
font.setPixelSize(conf::receipts::font);
auto displayName = TimelineViewManager::displayName(user_id);
auto displayName = Cache::displayName(room_id, user_id);
avatar_ = new Avatar(this);
avatar_->setSize(40);
@ -51,8 +55,10 @@ ReceiptItem::ReceiptItem(QWidget *parent, const QString &user_id, uint64_t times
topLayout_->addWidget(avatar_);
topLayout_->addLayout(textLayout_, 1);
AvatarProvider::resolve(
user_id, this, [this](const QImage &img) { avatar_->setImage(img); });
AvatarProvider::resolve(ChatPage::instance()->currentRoom(),
user_id,
this,
[this](const QImage &img) { avatar_->setImage(img); });
}
QString
@ -104,8 +110,10 @@ ReadReceipts::addUsers(const std::multimap<uint64_t, std::string, std::greater<u
userList_->clear();
for (const auto &receipt : receipts) {
auto user =
new ReceiptItem(this, QString::fromStdString(receipt.second), receipt.first);
auto user = new ReceiptItem(this,
QString::fromStdString(receipt.second),
receipt.first,
ChatPage::instance()->currentRoom());
auto item = new QListWidgetItem(userList_);
item->setSizeHint(user->minimumSizeHint());

@ -99,12 +99,14 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty,
const QString &userid,
QString body,
bool withSender,
const QString &room_id,
QWidget *parent)
: QWidget(parent)
, room_id_{room_id}
{
init();
auto displayName = TimelineViewManager::displayName(userid);
auto displayName = Cache::displayName(room_id_, userid);
auto timestamp = QDateTime::currentDateTime();
if (ty == mtx::events::MessageType::Emote) {
@ -127,7 +129,7 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty,
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
userid, this, [this](const QImage &img) { setUserAvatar(img); });
room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(body);
setupSimpleLayout();
@ -143,8 +145,10 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty,
TimelineItem::TimelineItem(ImageItem *image,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent)
: QWidget{parent}
, room_id_{room_id}
{
init();
@ -153,8 +157,13 @@ TimelineItem::TimelineItem(ImageItem *image,
addSaveImageAction(image);
}
TimelineItem::TimelineItem(FileItem *file, const QString &userid, bool withSender, QWidget *parent)
TimelineItem::TimelineItem(FileItem *file,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent)
: QWidget{parent}
, room_id_{room_id}
{
init();
@ -164,8 +173,10 @@ TimelineItem::TimelineItem(FileItem *file, const QString &userid, bool withSende
TimelineItem::TimelineItem(AudioItem *audio,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent)
: QWidget{parent}
, room_id_{room_id}
{
init();
@ -175,8 +186,10 @@ TimelineItem::TimelineItem(AudioItem *audio,
TimelineItem::TimelineItem(VideoItem *video,
const QString &userid,
bool withSender,
const QString &room_id,
QWidget *parent)
: QWidget{parent}
, room_id_{room_id}
{
init();
@ -186,8 +199,10 @@ TimelineItem::TimelineItem(VideoItem *video,
TimelineItem::TimelineItem(ImageItem *image,
const mtx::events::RoomEvent<mtx::events::msg::Image> &event,
bool with_sender,
const QString &room_id,
QWidget *parent)
: QWidget(parent)
, room_id_{room_id}
{
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Image>, ImageItem>(
image, event, " sent an image", with_sender);
@ -198,8 +213,10 @@ TimelineItem::TimelineItem(ImageItem *image,
TimelineItem::TimelineItem(FileItem *file,
const mtx::events::RoomEvent<mtx::events::msg::File> &event,
bool with_sender,
const QString &room_id,
QWidget *parent)
: QWidget(parent)
, room_id_{room_id}
{
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::File>, FileItem>(
file, event, " sent a file", with_sender);
@ -208,8 +225,10 @@ TimelineItem::TimelineItem(FileItem *file,
TimelineItem::TimelineItem(AudioItem *audio,
const mtx::events::RoomEvent<mtx::events::msg::Audio> &event,
bool with_sender,
const QString &room_id,
QWidget *parent)
: QWidget(parent)
, room_id_{room_id}
{
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Audio>, AudioItem>(
audio, event, " sent an audio clip", with_sender);
@ -218,8 +237,10 @@ TimelineItem::TimelineItem(AudioItem *audio,
TimelineItem::TimelineItem(VideoItem *video,
const mtx::events::RoomEvent<mtx::events::msg::Video> &event,
bool with_sender,
const QString &room_id,
QWidget *parent)
: QWidget(parent)
, room_id_{room_id}
{
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Video>, VideoItem>(
video, event, " sent a video clip", with_sender);
@ -230,8 +251,10 @@ TimelineItem::TimelineItem(VideoItem *video,
*/
TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &event,
bool with_sender,
const QString &room_id,
QWidget *parent)
: QWidget(parent)
, room_id_{room_id}
{
init();
@ -240,7 +263,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
const auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
auto body = QString::fromStdString(event.content.body).trimmed().toHtmlEscaped();
descriptionMsg_ = {TimelineViewManager::displayName(sender),
descriptionMsg_ = {Cache::displayName(room_id_, sender),
sender,
" sent a notification",
utils::descriptiveTime(timestamp),
@ -253,7 +276,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
body = "<i>" + body + "</i>";
if (with_sender) {
auto displayName = TimelineViewManager::displayName(sender);
auto displayName = Cache::displayName(room_id_, sender);
generateBody(displayName, body);
setupAvatarLayout(displayName);
@ -261,7 +284,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
sender, this, [this](const QImage &img) { setUserAvatar(img); });
room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(body);
setupSimpleLayout();
@ -279,8 +302,10 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
*/
TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> &event,
bool with_sender,
const QString &room_id,
QWidget *parent)
: QWidget(parent)
, room_id_{room_id}
{
init();
@ -289,7 +314,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
auto body = QString::fromStdString(event.content.body).trimmed();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
auto displayName = TimelineViewManager::displayName(sender);
auto displayName = Cache::displayName(room_id_, sender);
auto emoteMsg = QString("* %1 %2").arg(displayName).arg(body);
descriptionMsg_ = {"", sender, emoteMsg, utils::descriptiveTime(timestamp), timestamp};
@ -306,7 +331,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
sender, this, [this](const QImage &img) { setUserAvatar(img); });
room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(emoteMsg);
setupSimpleLayout();
@ -324,8 +349,10 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
*/
TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> &event,
bool with_sender,
const QString &room_id,
QWidget *parent)
: QWidget(parent)
, room_id_{room_id}
{
init();
@ -334,7 +361,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text>
auto body = QString::fromStdString(event.content.body).trimmed();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
auto displayName = TimelineViewManager::displayName(sender);
auto displayName = Cache::displayName(room_id_, sender);
QSettings settings;
descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName,
@ -356,7 +383,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text>
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
sender, this, [this](const QImage &img) { setUserAvatar(img); });
room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(body);
setupSimpleLayout();
@ -532,7 +559,7 @@ TimelineItem::addAvatar()
// TODO: should be replaced with the proper event struct.
auto userid = descriptionMsg_.userid;
auto displayName = TimelineViewManager::displayName(userid);
auto displayName = Cache::displayName(room_id_, userid);
QFontMetrics fm(usernameFont_);
userName_ = new QLabel(this);
@ -566,5 +593,6 @@ TimelineItem::addAvatar()
messageLayout_->addWidget(checkmark_);
messageLayout_->addWidget(timestamp_);
AvatarProvider::resolve(userid, this, [this](const QImage &img) { setUserAvatar(img); });
AvatarProvider::resolve(
room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); });
}

@ -477,8 +477,7 @@ TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
auto with_sender = lastSender_ != local_user_;
TimelineItem *view_item =
new TimelineItem(ty, local_user_, body, with_sender, scroll_widget_);
view_item->setRoomId(room_id_);
new TimelineItem(ty, local_user_, body, with_sender, room_id_, scroll_widget_);
addTimelineItem(view_item);
@ -538,7 +537,7 @@ TimelineView::notifyForLastEvent()
void
TimelineView::notifyForLastEvent(const TimelineEvent &event)
{
auto descInfo = utils::getMessageDescription(event, local_user_);
auto descInfo = utils::getMessageDescription(event, local_user_, room_id_);
if (!descInfo.timestamp.isEmpty())
emit updateLastTimelineMessage(room_id_, descInfo);

@ -172,18 +172,23 @@ TimelineViewManager::initialize(const mtx::responses::Rooms &rooms)
for (auto it = rooms.join.cbegin(); it != rooms.join.cend(); ++it) {
addRoom(it->second, QString::fromStdString(it->first));
}
sync(rooms);
}
void
TimelineViewManager::initialize(const std::vector<QString> &rooms)
TimelineViewManager::initialize(const std::vector<std::string> &rooms)
{
for (const auto &roomid : rooms)
addRoom(roomid);
addRoom(QString::fromStdString(roomid));
}
void
TimelineViewManager::addRoom(const mtx::responses::JoinedRoom &room, const QString &room_id)
{
if (timelineViewExists(room_id))
return;
// Create a history view with the room events.
TimelineView *view = new TimelineView(room.timeline, client_, room_id);
views_.emplace(room_id, QSharedPointer<TimelineView>(view));
@ -200,6 +205,9 @@ TimelineViewManager::addRoom(const mtx::responses::JoinedRoom &room, const QStri
void
TimelineViewManager::addRoom(const QString &room_id)
{
if (timelineViewExists(room_id))
return;
// Create a history view without any events.
TimelineView *view = new TimelineView(client_, room_id);
views_.emplace(room_id, QSharedPointer<TimelineView>(view));
@ -247,8 +255,6 @@ TimelineViewManager::setHistoryView(const QString &room_id)
view->scrollDown();
}
std::map<QString, QString> TimelineViewManager::DISPLAY_NAMES;
QString
TimelineViewManager::chooseRandomColor()
{
@ -307,15 +313,6 @@ TimelineViewManager::chooseRandomColor()
return color.name();
}
QString
TimelineViewManager::displayName(const QString &userid)
{
if (DISPLAY_NAMES.find(userid) != DISPLAY_NAMES.end())
return DISPLAY_NAMES.at(userid);
return userid;
}
bool
TimelineViewManager::hasLoaded() const
{

Loading…
Cancel
Save