diff --git a/resources/qml/ChatPage.qml b/resources/qml/ChatPage.qml
index 7f6758df..ae37187b 100644
--- a/resources/qml/ChatPage.qml
+++ b/resources/qml/ChatPage.qml
@@ -79,6 +79,7 @@ Rectangle {
showBackButton: adaptiveView.singlePageMode
room: Rooms.currentRoom
+ roomPreview: Rooms.currentRoomPreview.roomid ? Rooms.currentRoomPreview : null
}
}
diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index c4a8bcfb..a92beb38 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -121,7 +121,7 @@ Page {
states: [
State {
name: "highlight"
- when: hovered.hovered && !(Rooms.currentRoom && model.roomId == Rooms.currentRoom.roomId())
+ when: hovered.hovered && !((Rooms.currentRoom && model.roomId == Rooms.currentRoom.roomId()) || Rooms.currentRoomPreview.roomid == model.roomId)
PropertyChanges {
target: roomItem
@@ -135,7 +135,7 @@ Page {
},
State {
name: "selected"
- when: Rooms.currentRoom && model.roomId == Rooms.currentRoom.roomId()
+ when: (Rooms.currentRoom && model.roomId == Rooms.currentRoom.roomId()) || Rooms.currentRoomPreview.roomid == model.roomId
PropertyChanges {
target: roomItem
@@ -268,7 +268,7 @@ Page {
RowLayout {
Layout.fillWidth: true
spacing: 0
- visible: !model.isInvite && !model.isSpace
+ visible: !model.isSpace
height: visible ? 0 : undefined
ElidedLabel {
@@ -310,60 +310,6 @@ Page {
}
- RowLayout {
- Layout.fillWidth: true
- spacing: Nheko.paddingMedium
- visible: model.isInvite
- enabled: visible
- height: visible ? 0 : undefined
-
- ElidedLabel {
- elideWidth: textContent.width / 2 - 2 * Nheko.paddingMedium
- fullText: qsTr("Accept")
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- leftPadding: Nheko.paddingMedium
- rightPadding: Nheko.paddingMedium
- color: Nheko.colors.brightText
-
- TapHandler {
- onSingleTapped: Rooms.acceptInvite(model.roomId)
- }
-
- background: Rectangle {
- color: Nheko.theme.alternateButton
- radius: height / 2
- }
-
- }
-
- ElidedLabel {
- Layout.alignment: Qt.AlignRight
- elideWidth: textContent.width / 2 - 2 * Nheko.paddingMedium
- fullText: qsTr("Decline")
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- leftPadding: Nheko.paddingMedium
- rightPadding: Nheko.paddingMedium
- color: Nheko.colors.brightText
-
- TapHandler {
- onSingleTapped: Rooms.declineInvite(model.roomId)
- }
-
- background: Rectangle {
- color: Nheko.theme.alternateButton
- radius: height / 2
- }
-
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- }
-
}
}
diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml
index d6e6c6a5..c852b837 100644
--- a/resources/qml/RoomSettings.qml
+++ b/resources/qml/RoomSettings.qml
@@ -117,9 +117,11 @@ ApplicationWindow {
}
ScrollView {
- Layout.maximumHeight: 75
+ Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
- width: parent.width
+ Layout.fillWidth: true
+ Layout.leftMargin: Nheko.paddingLarge
+ Layout.rightMargin: Nheko.paddingLarge
TextArea {
text: TimelineManager.escapeEmoji(roomSettings.roomTopic)
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 0209054d..46317b2c 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
+import "./components"
import "./delegates"
import "./device-verification"
import "./emoji"
@@ -21,10 +22,11 @@ Item {
id: timelineView
property var room: null
+ property var roomPreview: null
property bool showBackButton: false
Label {
- visible: !room && !TimelineManager.isInitialSync
+ visible: !room && !TimelineManager.isInitialSync && !roomPreview
anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24
@@ -132,15 +134,25 @@ Item {
}
ColumnLayout {
- visible: room != null && room.isSpace
+ id: preview
+
+ property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "")
+ property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "")
+ property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
+
+ visible: room != null && room.isSpace || roomPreview != null
enabled: visible
anchors.fill: parent
anchors.margins: Nheko.paddingLarge
spacing: Nheko.paddingLarge
+ Item {
+ Layout.fillHeight: true
+ }
+
Avatar {
- url: room ? room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
- displayName: room ? room.roomName : ""
+ url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: parent.roomName
height: 130
width: 130
Layout.alignment: Qt.AlignHCenter
@@ -148,22 +160,25 @@ Item {
}
MatrixText {
- text: room ? room.roomName : ""
+ text: parent.roomName
font.pixelSize: 24
Layout.alignment: Qt.AlignHCenter
}
MatrixText {
+ visible: !!room
text: qsTr("%1 member(s)").arg(room ? room.roomMemberCount : 0)
Layout.alignment: Qt.AlignHCenter
}
ScrollView {
Layout.alignment: Qt.AlignHCenter
- width: timelineView.width - Nheko.paddingLarge * 2
+ Layout.fillWidth: true
+ Layout.leftMargin: Nheko.paddingLarge
+ Layout.rightMargin: Nheko.paddingLarge
TextArea {
- text: TimelineManager.escapeEmoji(room ? room.roomTopic : "")
+ text: TimelineManager.escapeEmoji(preview.roomTopic)
wrapMode: TextEdit.WordWrap
textFormat: TextEdit.RichText
readOnly: true
@@ -182,6 +197,32 @@ Item {
}
+ FlatButton {
+ visible: roomPreview && !roomPreview.isInvite
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("join the conversation")
+ onClicked: Rooms.joinPreview(roomPreview.roomid)
+ }
+
+ FlatButton {
+ visible: roomPreview && roomPreview.isInvite
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("accept invite")
+ onClicked: Rooms.acceptInvite(roomPreview.roomid)
+ }
+
+ FlatButton {
+ visible: roomPreview && roomPreview.isInvite
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("decline invite")
+ onClicked: Rooms.declineInvite(roomPreview.roomid)
+ }
+
+ Item {
+ visible: room != null
+ Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2)
+ }
+
Item {
Layout.fillHeight: true
}
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
index 30ab2e7c..82373023 100644
--- a/resources/qml/TopBar.qml
+++ b/resources/qml/TopBar.qml
@@ -12,6 +12,9 @@ Rectangle {
id: topBar
property bool showBackButton: false
+ property string roomName: room ? room.roomName : qsTr("No room selected")
+ property string avatarUrl: room ? room.roomAvatarUrl : ""
+ property string roomTopic: room ? room.roomTopic : ""
Layout.fillWidth: true
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
@@ -20,15 +23,15 @@ Rectangle {
TapHandler {
onSingleTapped: {
- room.openRoomSettings();
+ if (room)
+ room.openRoomSettings();
+
eventPoint.accepted = true;
}
gesturePolicy: TapHandler.ReleaseWithinBounds
}
GridLayout {
- //Layout.margins: 8
-
id: topLayout
anchors.left: parent.left
@@ -59,9 +62,13 @@ Rectangle {
Layout.alignment: Qt.AlignVCenter
width: Nheko.avatarSize
height: Nheko.avatarSize
- url: room ? room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
- displayName: room ? room.roomName : qsTr("No room selected")
- onClicked: room.openRoomSettings()
+ url: avatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: roomName
+ onClicked: {
+ if (room) {
+ room.openRoomSettings();
+ }
+ }
}
Label {
@@ -70,7 +77,7 @@ Rectangle {
Layout.row: 0
color: Nheko.colors.text
font.pointSize: fontMetrics.font.pointSize * 1.1
- text: room ? room.roomName : qsTr("No room selected")
+ text: roomName
maximumLineCount: 1
elide: Text.ElideRight
textFormat: Text.RichText
@@ -82,12 +89,13 @@ Rectangle {
Layout.row: 1
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
clip: true
- text: room ? room.roomTopic : ""
+ text: roomTopic
}
ImageButton {
id: roomOptionsButton
+ visible: !!room
Layout.column: 3
Layout.row: 0
Layout.rowSpan: 2
diff --git a/resources/qml/components/FlatButton.qml b/resources/qml/components/FlatButton.qml
new file mode 100644
index 00000000..77d97976
--- /dev/null
+++ b/resources/qml/components/FlatButton.qml
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtGraphicalEffects 1.12
+import QtQuick 2.9
+import QtQuick.Controls 2.5
+import im.nheko 1.0
+
+Button {
+ id: control
+
+ implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.5)
+ implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
+ hoverEnabled: true
+
+ DropShadow {
+ anchors.fill: control.background
+ horizontalOffset: 3
+ verticalOffset: 3
+ radius: 8
+ samples: 17
+ cached: true
+ color: "#80000000"
+ source: control.background
+ }
+
+ contentItem: Text {
+ text: control.text
+ //font: control.font
+ font.capitalization: Font.AllUppercase
+ font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5)
+ //font.capitalization: Font.AllUppercase
+ color: Nheko.colors.light
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ //height: control.contentItem.implicitHeight * 2
+ //width: control.contentItem.implicitWidth * 2
+ radius: height / 6
+ color: Qt.lighter(Nheko.colors.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
+ }
+
+}
diff --git a/resources/res.qrc b/resources/res.qrc
index 9bb8ae2e..f41835f9 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -183,6 +183,7 @@
qml/voip/VideoCall.qml
qml/components/AdaptiveLayout.qml
qml/components/AdaptiveLayoutElement.qml
+ qml/components/FlatButton.qml
media/ring.ogg
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 87940948..e2901260 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -16,6 +16,8 @@ RoomlistModel::RoomlistModel(TimelineViewManager *parent)
: QAbstractListModel(parent)
, manager(parent)
{
+ [[maybe_unused]] static auto id = qRegisterMetaType();
+
connect(ChatPage::instance(), &ChatPage::decryptSidebarChanged, this, [this]() {
auto decrypt = ChatPage::instance()->userSettings()->decryptSidebar();
QHash>::iterator i;
@@ -137,7 +139,7 @@ RoomlistModel::data(const QModelIndex &index, int role) const
case Roles::RoomName:
return QString::fromStdString(room.name);
case Roles::LastMessage:
- return QString();
+ return tr("Pending invite.");
case Roles::Time:
return QString();
case Roles::Timestamp:
@@ -434,9 +436,11 @@ void
RoomlistModel::sync(const mtx::responses::Rooms &rooms)
{
for (const auto &[room_id, room] : rooms.join) {
+ auto qroomid = QString::fromStdString(room_id);
+
// addRoom will only add the room, if it doesn't exist
- addRoom(QString::fromStdString(room_id));
- const auto &room_model = models.value(QString::fromStdString(room_id));
+ addRoom(qroomid);
+ const auto &room_model = models.value(qroomid);
room_model->sync(room);
// room_model->addEvents(room.timeline);
connect(room_model.data(),
@@ -465,14 +469,20 @@ RoomlistModel::sync(const mtx::responses::Rooms &rooms)
for (const auto &[room_id, room] : rooms.leave) {
(void)room;
- auto idx = this->roomidToIndex(QString::fromStdString(room_id));
+ auto qroomid = QString::fromStdString(room_id);
+
+ if ((currentRoom_ && currentRoom_->roomId() == qroomid) ||
+ (currentRoomPreview_ && currentRoomPreview_->roomid() == qroomid))
+ resetCurrentRoom();
+
+ auto idx = this->roomidToIndex(qroomid);
if (idx != -1) {
beginRemoveRows(QModelIndex(), idx, idx);
roomids.erase(roomids.begin() + idx);
- if (models.contains(QString::fromStdString(room_id)))
- models.remove(QString::fromStdString(room_id));
- else if (invites.contains(QString::fromStdString(room_id)))
- invites.remove(QString::fromStdString(room_id));
+ if (models.contains(qroomid))
+ models.remove(qroomid);
+ else if (invites.contains(qroomid))
+ invites.remove(qroomid);
endRemoveRows();
}
}
@@ -529,6 +539,19 @@ RoomlistModel::clear()
endResetModel();
}
+void
+RoomlistModel::joinPreview(QString roomid, QString parentSpace)
+{
+ if (previewedRooms.contains(roomid)) {
+ auto child = cache::client()->getStateEvent(
+ parentSpace.toStdString(), roomid.toStdString());
+ ChatPage::instance()->joinRoomVia(roomid.toStdString(),
+ (child && child->content.via)
+ ? child->content.via.value()
+ : std::vector{},
+ false);
+ }
+}
void
RoomlistModel::acceptInvite(QString roomid)
{
@@ -581,6 +604,31 @@ RoomlistModel::setCurrentRoom(QString roomid)
nhlog::ui()->debug("Trying to switch to: {}", roomid.toStdString());
if (models.contains(roomid)) {
currentRoom_ = models.value(roomid);
+ currentRoomPreview_.reset();
+ emit currentRoomChanged();
+ nhlog::ui()->debug("Switched to: {}", roomid.toStdString());
+ } else if (invites.contains(roomid) || previewedRooms.contains(roomid)) {
+ currentRoom_ = nullptr;
+ std::optional i;
+
+ RoomPreview p;
+
+ if (invites.contains(roomid)) {
+ i = invites.value(roomid);
+ p.isInvite_ = true;
+ } else {
+ i = previewedRooms.value(roomid);
+ p.isInvite_ = false;
+ }
+
+ if (i) {
+ p.roomid_ = roomid;
+ p.roomName_ = QString::fromStdString(i->name);
+ p.roomTopic_ = QString::fromStdString(i->topic);
+ p.roomAvatarUrl_ = QString::fromStdString(i->avatar_url);
+ currentRoomPreview_ = std::move(p);
+ }
+
emit currentRoomChanged();
nhlog::ui()->debug("Switched to: {}", roomid.toStdString());
}
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index 2005c35e..6ac6da18 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -18,11 +18,35 @@
class TimelineViewManager;
+class RoomPreview
+{
+ Q_GADGET
+ Q_PROPERTY(QString roomid READ roomid CONSTANT)
+ Q_PROPERTY(QString roomName READ roomName CONSTANT)
+ Q_PROPERTY(QString roomTopic READ roomTopic CONSTANT)
+ Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl CONSTANT)
+ Q_PROPERTY(bool isInvite READ isInvite CONSTANT)
+
+public:
+ RoomPreview() {}
+
+ QString roomid() const { return roomid_; }
+ QString roomName() const { return roomName_; }
+ QString roomTopic() const { return roomTopic_; }
+ QString roomAvatarUrl() const { return roomAvatarUrl_; }
+ bool isInvite() const { return isInvite_; }
+
+ QString roomid_, roomName_, roomAvatarUrl_, roomTopic_;
+ bool isInvite_ = false;
+};
+
class RoomlistModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET
resetCurrentRoom)
+ Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged
+ RESET resetCurrentRoom)
public:
enum Roles
{
@@ -72,14 +96,20 @@ public slots:
return -1;
}
+ void joinPreview(QString roomid, QString parentSpace);
void acceptInvite(QString roomid);
void declineInvite(QString roomid);
void leave(QString roomid);
TimelineModel *currentRoom() const { return currentRoom_.get(); }
+ RoomPreview currentRoomPreview() const
+ {
+ return currentRoomPreview_.value_or(RoomPreview{});
+ }
void setCurrentRoom(QString roomid);
void resetCurrentRoom()
{
currentRoom_ = nullptr;
+ currentRoomPreview_.reset();
emit currentRoomChanged();
}
@@ -103,6 +133,7 @@ private:
QHash> previewedRooms;
QSharedPointer currentRoom_;
+ std::optional currentRoomPreview_;
friend class FilteredRoomlistModel;
};
@@ -112,6 +143,8 @@ class FilteredRoomlistModel : public QSortFilterProxyModel
Q_OBJECT
Q_PROPERTY(TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET
resetCurrentRoom)
+ Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged
+ RESET resetCurrentRoom)
public:
FilteredRoomlistModel(RoomlistModel *model, QObject *parent = nullptr);
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
@@ -123,12 +156,17 @@ public slots:
return mapFromSource(roomlistmodel->index(roomlistmodel->roomidToIndex(roomid)))
.row();
}
+ void joinPreview(QString roomid)
+ {
+ roomlistmodel->joinPreview(roomid, filterType == FilterBy::Space ? filterStr : "");
+ }
void acceptInvite(QString roomid) { roomlistmodel->acceptInvite(roomid); }
void declineInvite(QString roomid) { roomlistmodel->declineInvite(roomid); }
void leave(QString roomid) { roomlistmodel->leave(roomid); }
void toggleTag(QString roomid, QString tag, bool on);
TimelineModel *currentRoom() const { return roomlistmodel->currentRoom(); }
+ RoomPreview currentRoomPreview() const { return roomlistmodel->currentRoomPreview(); }
void setCurrentRoom(QString roomid) { roomlistmodel->setCurrentRoom(std::move(roomid)); }
void resetCurrentRoom() { roomlistmodel->resetCurrentRoom(); }