diff --git a/include/ChatPage.h b/include/ChatPage.h
index 12eaf6b7..68495276 100644
--- a/include/ChatPage.h
+++ b/include/ChatPage.h
@@ -59,6 +59,7 @@ private slots:
void logout();
private:
+ void updateDisplayNames(const RoomState &state);
void updateRoomState(RoomState &room_state, const QJsonArray &events);
QHBoxLayout *topLayout_;
diff --git a/include/RoomInfoListItem.h b/include/RoomInfoListItem.h
index 75a90ff3..17c75fc3 100644
--- a/include/RoomInfoListItem.h
+++ b/include/RoomInfoListItem.h
@@ -50,8 +50,8 @@ protected:
void paintEvent(QPaintEvent *event) override;
private:
- const int Padding = 10;
- const int IconSize = 45;
+ const int Padding = 7;
+ const int IconSize = 46;
RippleOverlay *ripple_overlay_;
@@ -66,7 +66,7 @@ private:
bool isPressed_ = false;
- int maxHeight_ = 60;
+ int maxHeight_;
int unreadMsgCount_ = 0;
};
@@ -87,5 +87,5 @@ inline RoomState RoomInfoListItem::state() const
inline void RoomInfoListItem::setAvatar(const QImage &img)
{
- roomAvatar_ = QPixmap::fromImage(img.scaled(IconSize, IconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
+ roomAvatar_ = QPixmap::fromImage(img.scaled(IconSize, IconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
diff --git a/include/RoomState.h b/include/RoomState.h
index 788940e2..0389a6df 100644
--- a/include/RoomState.h
+++ b/include/RoomState.h
@@ -18,6 +18,7 @@
#pragma once
#include <QPixmap>
+#include <QUrl>
#include "AliasesEventContent.h"
#include "AvatarEventContent.h"
@@ -25,6 +26,7 @@
#include "CreateEventContent.h"
#include "HistoryVisibilityEventContent.h"
#include "JoinRulesEventContent.h"
+#include "MemberEventContent.h"
#include "NameEventContent.h"
#include "PowerLevelsEventContent.h"
#include "TopicEventContent.h"
@@ -38,11 +40,20 @@ namespace events = matrix::events;
class RoomState
{
public:
- QString resolveName() const;
- inline QString resolveTopic() const;
+ // Calculate room data that are not immediatly accessible. Like room name and avatar.
+ //
+ // e.g If the room is 1-on-1 name and avatar should be extracted from a user.
+ void resolveName();
+ void resolveAvatar();
- QPixmap avatar_img_;
+ inline QUrl getAvatar() const;
+ inline QString getName() const;
+ inline QString getTopic() const;
+ void removeLeaveMemberships();
+ void update(const RoomState &state);
+
+ // The latest state events.
events::StateEvent<events::AliasesEventContent> aliases;
events::StateEvent<events::AvatarEventContent> avatar;
events::StateEvent<events::CanonicalAliasEventContent> canonical_alias;
@@ -52,9 +63,30 @@ public:
events::StateEvent<events::NameEventContent> name;
events::StateEvent<events::PowerLevelsEventContent> power_levels;
events::StateEvent<events::TopicEventContent> topic;
+
+ // Contains the m.room.member events for all the joined users.
+ QMap<QString, events::StateEvent<events::MemberEventContent>> memberships;
+
+private:
+ QUrl avatar_;
+ QString name_;
+
+ // It defines the user whose avatar is used for the room. If the room has an avatar
+ // event this should be empty.
+ QString userAvatar_;
};
-inline QString RoomState::resolveTopic() const
+inline QString RoomState::getTopic() const
{
return topic.content().topic().simplified();
}
+
+inline QString RoomState::getName() const
+{
+ return name_;
+}
+
+inline QUrl RoomState::getAvatar() const
+{
+ return avatar_;
+}
diff --git a/include/events/MemberEventContent.h b/include/events/MemberEventContent.h
index f3714462..e61d0cda 100644
--- a/include/events/MemberEventContent.h
+++ b/include/events/MemberEventContent.h
@@ -28,19 +28,19 @@ namespace events
{
enum class Membership {
// The user is banned.
- BanState,
+ Ban,
// The user has been invited.
- InviteState,
+ Invite,
// The user has joined.
- JoinState,
+ Join,
// The user has requested to join.
- KnockState,
+ Knock,
// The user has left.
- LeaveState,
+ Leave,
};
/*
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index b5012818..d318d086 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -220,6 +220,17 @@ void ChatPage::syncFailed(const QString &msg)
sync_timer_->start(sync_interval_ * 5);
}
+// TODO: Should be moved in another class that manages this global list.
+void ChatPage::updateDisplayNames(const RoomState &state)
+{
+ for (const auto member : state.memberships) {
+ auto displayName = member.content().displayName();
+
+ if (!displayName.isEmpty())
+ TimelineViewManager::DISPLAY_NAMES.insert(member.stateKey(), displayName);
+ }
+}
+
void ChatPage::syncCompleted(const SyncResponse &response)
{
client_->setNextBatchToken(response.nextBatch());
@@ -234,8 +245,16 @@ void ChatPage::syncCompleted(const SyncResponse &response)
updateRoomState(room_state, it.value().state().events());
updateRoomState(room_state, it.value().timeline().events());
+ updateDisplayNames(room_state);
- state_manager_.insert(it.key(), room_state);
+ if (state_manager_.contains(it.key())) {
+ // TODO: Use pointers instead of copying.
+ auto oldState = state_manager_[it.key()];
+ oldState.update(room_state);
+ state_manager_.insert(it.key(), oldState);
+ } else {
+ qWarning() << "New rooms cannot be added after initial sync, yet.";
+ }
if (it.key() == current_room_)
changeTopRoomInfo(it.key());
@@ -260,6 +279,12 @@ void ChatPage::initialSyncCompleted(const SyncResponse &response)
updateRoomState(room_state, it.value().state().events());
updateRoomState(room_state, it.value().timeline().events());
+ room_state.removeLeaveMemberships();
+ room_state.resolveName();
+ room_state.resolveAvatar();
+
+ updateDisplayNames(room_state);
+
state_manager_.insert(it.key(), room_state);
}
@@ -298,13 +323,13 @@ void ChatPage::changeTopRoomInfo(const QString &room_id)
auto state = state_manager_[room_id];
- top_bar_->updateRoomName(state.resolveName());
- top_bar_->updateRoomTopic(state.resolveTopic());
+ top_bar_->updateRoomName(state.getName());
+ top_bar_->updateRoomTopic(state.getTopic());
if (room_avatars_.contains(room_id))
top_bar_->updateRoomAvatar(room_avatars_.value(room_id).toImage());
else
- top_bar_->updateRoomAvatarFromName(state.resolveName());
+ top_bar_->updateRoomAvatarFromName(state.getName());
current_room_ = room_id;
}
@@ -383,15 +408,7 @@ void ChatPage::updateRoomState(RoomState &room_state, const QJsonArray &events)
events::StateEvent<events::MemberEventContent> member;
member.deserialize(event);
- auto display_name = member.content().displayName();
-
- if (display_name.isEmpty())
- display_name = member.stateKey();
-
- auto current_name = TimelineViewManager::DISPLAY_NAMES.value(member.stateKey());
-
- if (current_name.isEmpty() || current_name == member.stateKey())
- TimelineViewManager::DISPLAY_NAMES.insert(member.stateKey(), display_name);
+ room_state.memberships.insert(member.stateKey(), member);
break;
}
diff --git a/src/RoomInfoListItem.cc b/src/RoomInfoListItem.cc
index 9385f927..7753536e 100644
--- a/src/RoomInfoListItem.cc
+++ b/src/RoomInfoListItem.cc
@@ -29,7 +29,7 @@ RoomInfoListItem::RoomInfoListItem(RoomState state, QString room_id, QWidget *pa
, state_(state)
, roomId_(room_id)
, isPressed_(false)
- , maxHeight_(60)
+ , maxHeight_(IconSize + 2 * Padding)
, unreadMsgCount_(0)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
@@ -71,7 +71,7 @@ void RoomInfoListItem::paintEvent(QPaintEvent *event)
QRect avatarRegion(Padding, Padding, IconSize, IconSize);
// Description line
- int bottom_y = avatarRegion.center().y() + metrics.height() / 2 + Padding / 2;
+ int bottom_y = maxHeight_ - Padding - metrics.height() / 2 + Padding / 2;
if (width() > ui::sidebar::SmallSize) {
if (isPressed_) {
@@ -79,8 +79,8 @@ void RoomInfoListItem::paintEvent(QPaintEvent *event)
p.setPen(pen);
}
- auto name = metrics.elidedText(state_.resolveName(), Qt::ElideRight, (width() - IconSize - 2 * Padding) * 0.8);
- p.drawText(QPoint(2 * Padding + IconSize, avatarRegion.center().y() - metrics.height() / 2), name);
+ auto name = metrics.elidedText(state_.getName(), Qt::ElideRight, (width() - IconSize - 2 * Padding) * 0.8);
+ p.drawText(QPoint(2 * Padding + IconSize, Padding + metrics.height()), name);
if (!isPressed_) {
QPen pen(QColor("#5d6565"));
@@ -92,7 +92,7 @@ void RoomInfoListItem::paintEvent(QPaintEvent *event)
if (unreadMsgCount_ > 0)
descPercentage = 0.8;
- auto description = metrics.elidedText(state_.resolveTopic(), Qt::ElideRight, width() * descPercentage - 2 * Padding - IconSize);
+ auto description = metrics.elidedText(state_.getTopic(), Qt::ElideRight, width() * descPercentage - 2 * Padding - IconSize);
p.drawText(QPoint(2 * Padding + IconSize, bottom_y), description);
}
@@ -113,7 +113,7 @@ void RoomInfoListItem::paintEvent(QPaintEvent *event)
p.setFont(font);
p.setPen(QColor("#333"));
p.setBrush(Qt::NoBrush);
- p.drawText(avatarRegion.translated(0, -1), Qt::AlignCenter, QChar(state_.resolveName()[0]));
+ p.drawText(avatarRegion.translated(0, -1), Qt::AlignCenter, QChar(state_.getName()[0]));
} else {
p.save();
diff --git a/src/RoomList.cc b/src/RoomList.cc
index 3e381340..6d0e185b 100644
--- a/src/RoomList.cc
+++ b/src/RoomList.cc
@@ -100,8 +100,8 @@ void RoomList::setInitialRooms(const QMap<QString, RoomState> &states)
auto room_id = it.key();
auto state = it.value();
- if (!state.avatar.content().url().toString().isEmpty())
- client_->fetchRoomAvatar(room_id, state.avatar.content().url());
+ if (!state.getAvatar().toString().isEmpty())
+ client_->fetchRoomAvatar(room_id, state.getAvatar());
RoomInfoListItem *room_item = new RoomInfoListItem(state, room_id, scrollArea_);
connect(room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom);
@@ -133,8 +133,8 @@ void RoomList::sync(const QMap<QString, RoomState> &states)
auto room = rooms_[room_id];
- auto current_avatar = room->state().avatar.content().url();
- auto new_avatar = state.avatar.content().url();
+ auto current_avatar = room->state().getAvatar();
+ auto new_avatar = state.getAvatar();
if (current_avatar != new_avatar && !new_avatar.toString().isEmpty())
client_->fetchRoomAvatar(room_id, new_avatar);
diff --git a/src/RoomState.cc b/src/RoomState.cc
index 98f418e3..3eaff452 100644
--- a/src/RoomState.cc
+++ b/src/RoomState.cc
@@ -15,18 +15,138 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <QDebug>
+#include <QSettings>
+
#include "RoomState.h"
-QString RoomState::resolveName() const
+namespace events = matrix::events;
+
+void RoomState::resolveName()
{
- if (!name.content().name().isEmpty())
- return name.content().name().simplified();
+ name_ = "Empty Room";
+ userAvatar_.clear();
+
+ if (!name.content().name().isEmpty()) {
+ name_ = name.content().name().simplified();
+ return;
+ }
+
+ if (!canonical_alias.content().alias().isEmpty()) {
+ name_ = canonical_alias.content().alias().simplified();
+ return;
+ }
+
+ // FIXME: Doesn't follow the spec guidelines.
+ if (aliases.content().aliases().size() != 0) {
+ name_ = aliases.content().aliases()[0].simplified();
+ return;
+ }
+
+ QSettings settings;
+ auto user_id = settings.value("auth/user_id");
+
+ // TODO: Display names should be sorted alphabetically.
+ for (const auto membership : memberships) {
+ if (membership.stateKey() == user_id)
+ continue;
+
+ if (membership.content().membershipState() == events::Membership::Join) {
+ userAvatar_ = membership.stateKey();
+
+ if (membership.content().displayName().isEmpty())
+ name_ = membership.stateKey();
+ else
+ name_ = membership.content().displayName();
+
+ break;
+ }
+ }
+
+ // TODO: pluralization
+ if (memberships.size() > 2)
+ name_ = QString("%1 and %2 others").arg(name_).arg(memberships.size());
+}
+
+void RoomState::resolveAvatar()
+{
+ if (userAvatar_.isEmpty()) {
+ avatar_ = avatar.content().url();
+ return;
+ }
+
+ if (memberships.contains(userAvatar_)) {
+ avatar_ = memberships[userAvatar_].content().avatarUrl();
+ } else {
+ qWarning() << "Setting room avatar from unknown user id" << userAvatar_;
+ }
+}
+
+// Should be used only after initial sync.
+void RoomState::removeLeaveMemberships()
+{
+ for (auto it = memberships.begin(); it != memberships.end();) {
+ if (it.value().content().membershipState() == events::Membership::Leave) {
+ it = memberships.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+void RoomState::update(const RoomState &state)
+{
+ bool needsNameCalculation = false;
+ bool needsAvatarCalculation = false;
+
+ if (aliases.eventId() != state.aliases.eventId()) {
+ aliases = state.aliases;
+ }
+
+ if (avatar.eventId() != state.avatar.eventId()) {
+ avatar = state.avatar;
+ needsAvatarCalculation = true;
+ }
+
+ if (canonical_alias.eventId() != state.canonical_alias.eventId()) {
+ canonical_alias = state.canonical_alias;
+ needsNameCalculation = true;
+ }
+
+ if (create.eventId() != state.create.eventId())
+ create = state.create;
+ if (history_visibility.eventId() != state.history_visibility.eventId())
+ history_visibility = state.history_visibility;
+ if (join_rules.eventId() != state.join_rules.eventId())
+ join_rules = state.join_rules;
+
+ if (name.eventId() != state.name.eventId()) {
+ name = state.name;
+ needsNameCalculation = true;
+ }
+
+ if (power_levels.eventId() != state.power_levels.eventId())
+ power_levels = state.power_levels;
+ if (topic.eventId() != state.topic.eventId())
+ topic = state.topic;
+
+ for (auto it = state.memberships.constBegin(); it != state.memberships.constEnd(); ++it) {
+ auto membershipState = it.value().content().membershipState();
+
+ if (it.key() == userAvatar_) {
+ needsNameCalculation = true;
+ needsAvatarCalculation = true;
+ }
- if (!canonical_alias.content().alias().isEmpty())
- return canonical_alias.content().alias().simplified();
+ if (membershipState == events::Membership::Leave)
+ this->memberships.remove(it.key());
+ else
+ this->memberships.insert(it.key(), it.value());
+ }
- if (aliases.content().aliases().size() != 0)
- return aliases.content().aliases()[0].simplified();
+ if (needsNameCalculation)
+ resolveName();
- return "Unknown Room Name";
+ if (needsAvatarCalculation)
+ resolveAvatar();
}
diff --git a/src/events/MemberEventContent.cc b/src/events/MemberEventContent.cc
index 4c405f01..4dc8ad5f 100644
--- a/src/events/MemberEventContent.cc
+++ b/src/events/MemberEventContent.cc
@@ -34,15 +34,15 @@ void MemberEventContent::deserialize(const QJsonValue &data)
auto value = object.value("membership").toString();
if (value == "ban")
- membership_state_ = Membership::BanState;
+ membership_state_ = Membership::Ban;
else if (value == "invite")
- membership_state_ = Membership::InviteState;
+ membership_state_ = Membership::Invite;
else if (value == "join")
- membership_state_ = Membership::JoinState;
+ membership_state_ = Membership::Join;
else if (value == "knock")
- membership_state_ = Membership::KnockState;
+ membership_state_ = Membership::Knock;
else if (value == "leave")
- membership_state_ = Membership::LeaveState;
+ membership_state_ = Membership::Leave;
else
throw DeserializationException(QString("Unknown membership value: %1").arg(value).toUtf8().constData());
|