summary refs log tree commit diff
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2023-02-24 02:40:14 +0100
committerNicolas Werner <nicolas.werner@hotmail.de>2023-02-24 02:40:14 +0100
commitaae3300860ebe2fac39a156a31f9cddeefb4bf92 (patch)
treede63d71abe28e9e38e4cfb2c877ce5840e9b6a13
parentReenable the nosync options for the database (diff)
downloadnheko-aae3300860ebe2fac39a156a31f9cddeefb4bf92.tar.xz
Show rooms you share with someone
-rw-r--r--resources/qml/components/NhekoTabButton.qml25
-rw-r--r--resources/qml/dialogs/PowerLevelEditor.qml24
-rw-r--r--resources/qml/dialogs/UserProfile.qml286
-rw-r--r--resources/res.qrc1
-rw-r--r--src/Cache.cpp30
-rw-r--r--src/Cache_p.h1
-rw-r--r--src/ui/UserProfile.cpp47
-rw-r--r--src/ui/UserProfile.h27
8 files changed, 311 insertions, 130 deletions
diff --git a/resources/qml/components/NhekoTabButton.qml b/resources/qml/components/NhekoTabButton.qml
new file mode 100644
index 00000000..5ae8748b
--- /dev/null
+++ b/resources/qml/components/NhekoTabButton.qml
@@ -0,0 +1,25 @@
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import im.nheko 1.0
+
+TabButton {
+    id: control
+
+    contentItem: Text {
+        text: control.text
+        font: control.font
+        opacity: enabled ? 1.0 : 0.3
+        color: control.down ? Nheko.colors.highlightedText : Nheko.colors.text
+        horizontalAlignment: Text.AlignHCenter
+        verticalAlignment: Text.AlignVCenter
+        elide: Text.ElideRight
+    }
+
+    background: Rectangle {
+        border.color: control.down ? Nheko.colors.highlight : Nheko.theme.separator
+        color: control.checked ? Nheko.colors.highlight : Nheko.colors.base
+        border.width: 1
+        radius: 2
+    }
+}
+
diff --git a/resources/qml/dialogs/PowerLevelEditor.qml b/resources/qml/dialogs/PowerLevelEditor.qml
index 12458f62..048672e4 100644
--- a/resources/qml/dialogs/PowerLevelEditor.qml
+++ b/resources/qml/dialogs/PowerLevelEditor.qml
@@ -49,30 +49,10 @@ ApplicationWindow {
             width: parent.width
             palette: Nheko.colors
 
-            component TabB : TabButton {
-                id: control
-
-                contentItem: Text {
-                    text: control.text
-                    font: control.font
-                    opacity: enabled ? 1.0 : 0.3
-                    color: control.down ? Nheko.colors.highlightedText : Nheko.colors.text
-                    horizontalAlignment: Text.AlignHCenter
-                    verticalAlignment: Text.AlignVCenter
-                    elide: Text.ElideRight
-                }
-
-                background: Rectangle {
-                    border.color: control.down ? Nheko.colors.highlight : Nheko.theme.separator
-                    color: control.checked ? Nheko.colors.highlight : Nheko.colors.base
-                    border.width: 1
-                    radius: 2
-                }
-            }
-            TabB {
+            NhekoTabButton {
                 text: qsTr("Roles")
             }
-            TabB {
+            NhekoTabButton {
                 text: qsTr("Users")
             }
         }
diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml
index c0d4905b..792dec00 100644
--- a/resources/qml/dialogs/UserProfile.qml
+++ b/resources/qml/dialogs/UserProfile.qml
@@ -5,10 +5,12 @@
 import ".."
 import "../device-verification"
 import "../ui"
+import "../components"
 import QtQuick 2.15
 import QtQuick.Controls 2.15
 import QtQuick.Layouts 1.2
 import QtQuick.Window 2.13
+import QtQml.Models 2.2
 import im.nheko 1.0
 
 ApplicationWindow {
@@ -34,12 +36,13 @@ ApplicationWindow {
     ListView {
         id: devicelist
 
+        property int selectedTab: 0
+
         Layout.fillHeight: true
         Layout.fillWidth: true
         clip: true
         spacing: 8
         boundsBehavior: Flickable.StopAtBounds
-        model: profile.deviceList
         anchors.fill: parent
         anchors.margins: 10
         footerPositioning: ListView.OverlayFooter
@@ -297,147 +300,214 @@ ApplicationWindow {
 
             }
 
+            TabBar {
+                id: tabbar
+                visible: !profile.isSelf
+                Layout.fillWidth: true
+
+                onCurrentIndexChanged: devicelist.selectedTab = currentIndex
+
+                palette: Nheko.colors
+
+                NhekoTabButton {
+                    text: qsTr("Devices")
+                }
+                NhekoTabButton {
+                    text: qsTr("Shared Rooms")
+                }
+
+                Layout.bottomMargin: Nheko.paddingMedium
+            }
         }
 
-        delegate: RowLayout {
-            required property int verificationStatus
-            required property string deviceId
-            required property string deviceName
-            required property string lastIp
-            required property var lastTs
+        model: (selectedTab == 0) ? devicesModel : sharedRoomsModel
+
+        DelegateModel {
+            id: devicesModel
+            model: profile.deviceList
+            delegate: RowLayout {
+                required property int verificationStatus
+                required property string deviceId
+                required property string deviceName
+                required property string lastIp
+                required property var lastTs
+
+                width: devicelist.width
+                spacing: 4
+
+                ColumnLayout {
+                    spacing: 0
+
+                    Layout.leftMargin: Nheko.paddingMedium
+                    Layout.rightMargin: Nheko.paddingMedium
+                    RowLayout {
+                        Text {
+                            Layout.fillWidth: true
+                            Layout.alignment: Qt.AlignLeft
+                            elide: Text.ElideRight
+                            font.bold: true
+                            color: Nheko.colors.text
+                            text: deviceId
+                        }
 
-            width: devicelist.width
-            spacing: 4
+                        Image {
+                            Layout.preferredHeight: 16
+                            Layout.preferredWidth: 16
+                            visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
+                            sourceSize.height: 16 * Screen.devicePixelRatio
+                            sourceSize.width: 16 * Screen.devicePixelRatio
+                            source: {
+                                switch (verificationStatus) {
+                                    case VerificationStatus.VERIFIED:
+                                    return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green;
+                                    case VerificationStatus.UNVERIFIED:
+                                    return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange;
+                                    case VerificationStatus.SELF:
+                                    return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green;
+                                    default:
+                                    return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.orange;
+                                }
+                            }
+                        }
 
-            ColumnLayout {
-                spacing: 0
+                        ImageButton {
+                            Layout.alignment: Qt.AlignTop
+                            image: ":/icons/icons/ui/power-off.svg"
+                            hoverEnabled: true
+                            ToolTip.visible: hovered
+                            ToolTip.text: qsTr("Sign out this device.")
+                            onClicked: profile.signOutDevice(deviceId)
+                            visible: profile.isSelf
+                        }
+
+                    }
+
+                    RowLayout {
+                        id: deviceNameRow
+
+                        property bool isEditingAllowed
+
+                        TextInput {
+                            id: deviceNameField
+
+                            readOnly: !deviceNameRow.isEditingAllowed
+                            text: deviceName
+                            color: Nheko.colors.text
+                            Layout.alignment: Qt.AlignLeft
+                            Layout.fillWidth: true
+                            selectByMouse: true
+                            onAccepted: {
+                                profile.changeDeviceName(deviceId, deviceNameField.text);
+                                deviceNameRow.isEditingAllowed = false;
+                            }
+                        }
+
+                        ImageButton {
+                            visible: profile.isSelf
+                            hoverEnabled: true
+                            ToolTip.visible: hovered
+                            ToolTip.text: qsTr("Change device name.")
+                            image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
+                            onClicked: {
+                                if (deviceNameRow.isEditingAllowed) {
+                                    profile.changeDeviceName(deviceId, deviceNameField.text);
+                                    deviceNameRow.isEditingAllowed = false;
+                                } else {
+                                    deviceNameRow.isEditingAllowed = true;
+                                    deviceNameField.focus = true;
+                                    deviceNameField.selectAll();
+                                }
+                            }
+                        }
+
+                    }
 
-                Layout.leftMargin: Nheko.paddingMedium
-                Layout.rightMargin: Nheko.paddingMedium
-                RowLayout {
                     Text {
+                        visible: profile.isSelf
                         Layout.fillWidth: true
                         Layout.alignment: Qt.AlignLeft
                         elide: Text.ElideRight
-                        font.bold: true
                         color: Nheko.colors.text
-                        text: deviceId
+                        text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???")
                     }
 
-                    Image {
-                        Layout.preferredHeight: 16
-                        Layout.preferredWidth: 16
-                        visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
-                        sourceSize.height: 16 * Screen.devicePixelRatio
-                        sourceSize.width: 16 * Screen.devicePixelRatio
-                        source: {
-                            switch (verificationStatus) {
+                }
+
+                Image {
+                    Layout.preferredHeight: 16
+                    Layout.preferredWidth: 16
+                    visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
+                    source: {
+                        switch (verificationStatus) {
                             case VerificationStatus.VERIFIED:
-                                return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green;
+                            return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green;
                             case VerificationStatus.UNVERIFIED:
-                                return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange;
+                            return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange;
                             case VerificationStatus.SELF:
-                                return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green;
+                            return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green;
                             default:
-                                return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.orange;
-                            }
+                            return "image://colorimage/:/icons/icons/ui/shield-filled.svg?" + Nheko.theme.red;
                         }
                     }
+                }
 
-                    ImageButton {
-                        Layout.alignment: Qt.AlignTop
-                        image: ":/icons/icons/ui/power-off.svg"
-                        hoverEnabled: true
-                        ToolTip.visible: hovered
-                        ToolTip.text: qsTr("Sign out this device.")
-                        onClicked: profile.signOutDevice(deviceId)
-                        visible: profile.isSelf
-                    }
+                Button {
+                    id: verifyButton
 
+                    visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled)
+                    text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
+                    onClicked: {
+                        if (verificationStatus == VerificationStatus.VERIFIED)
+                        profile.unverify(deviceId);
+                        else
+                        profile.verify(deviceId);
+                    }
                 }
 
-                RowLayout {
-                    id: deviceNameRow
+            }
+        }
 
-                    property bool isEditingAllowed
+        DelegateModel {
+            id: sharedRoomsModel
+            model: profile.sharedRooms
+            delegate: RowLayout {
+                required property string roomId
+                required property string roomName
+                required property string avatarUrl
 
-                    TextInput {
-                        id: deviceNameField
+                width: devicelist.width
+                spacing: 4
 
-                        readOnly: !deviceNameRow.isEditingAllowed
-                        text: deviceName
-                        color: Nheko.colors.text
-                        Layout.alignment: Qt.AlignLeft
-                        Layout.fillWidth: true
-                        selectByMouse: true
-                        onAccepted: {
-                            profile.changeDeviceName(deviceId, deviceNameField.text);
-                            deviceNameRow.isEditingAllowed = false;
-                        }
-                    }
 
-                    ImageButton {
-                        visible: profile.isSelf
-                        hoverEnabled: true
-                        ToolTip.visible: hovered
-                        ToolTip.text: qsTr("Change device name.")
-                        image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
-                        onClicked: {
-                            if (deviceNameRow.isEditingAllowed) {
-                                profile.changeDeviceName(deviceId, deviceNameField.text);
-                                deviceNameRow.isEditingAllowed = false;
-                            } else {
-                                deviceNameRow.isEditingAllowed = true;
-                                deviceNameField.focus = true;
-                                deviceNameField.selectAll();
-                            }
-                        }
-                    }
+                Avatar {
+                    id: avatar
 
-                }
+                    enabled: false
+                    Layout.alignment: Qt.AlignVCenter
+                    Layout.leftMargin: Nheko.paddingMedium
 
-                Text {
-                    visible: profile.isSelf
-                    Layout.fillWidth: true
-                    Layout.alignment: Qt.AlignLeft
-                    elide: Text.ElideRight
-                    color: Nheko.colors.text
-                    text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???")
+                    property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
+                    height: avatarSize
+                    width: avatarSize
+                    url: avatarUrl.replace("mxc://", "image://MxcImage/")
+                    roomid: roomId
+                    displayName: roomName
                 }
 
-            }
-
-            Image {
-                Layout.preferredHeight: 16
-                Layout.preferredWidth: 16
-                visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
-                source: {
-                    switch (verificationStatus) {
-                    case VerificationStatus.VERIFIED:
-                        return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green;
-                    case VerificationStatus.UNVERIFIED:
-                        return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange;
-                    case VerificationStatus.SELF:
-                        return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green;
-                    default:
-                        return "image://colorimage/:/icons/icons/ui/shield-filled.svg?" + Nheko.theme.red;
-                    }
+                ElidedLabel {
+                    Layout.alignment: Qt.AlignVCenter
+                    color: Nheko.colors.text
+                    Layout.fillWidth: true
+                    elideWidth: width
+                    fullText: roomName
+                    textFormat: Text.PlainText
+                    Layout.rightMargin: Nheko.paddingMedium
                 }
-            }
-
-            Button {
-                id: verifyButton
 
-                visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled)
-                text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
-                onClicked: {
-                    if (verificationStatus == VerificationStatus.VERIFIED)
-                        profile.unverify(deviceId);
-                    else
-                        profile.verify(deviceId);
+                Item {
+                    Layout.fillWidth: true
                 }
             }
-
         }
 
         footer: DialogButtonBox {
diff --git a/resources/res.qrc b/resources/res.qrc
index 9663b5a3..88159d40 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -129,6 +129,7 @@
         <file>qml/components/AvatarListTile.qml</file>
         <file>qml/components/FlatButton.qml</file>
         <file>qml/components/MainWindowDialog.qml</file>
+        <file>qml/components/NhekoTabButton.qml</file>
         <file>qml/components/NotificationBubble.qml</file>
         <file>qml/components/ReorderableListview.qml</file>
         <file>qml/components/SpaceMenuLevel.qml</file>
diff --git a/src/Cache.cpp b/src/Cache.cpp
index b27a8b37..6c746d4b 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -3146,6 +3146,36 @@ Cache::joinedRooms()
     return room_ids;
 }
 
+std::map<std::string, RoomInfo>
+Cache::getCommonRooms(const std::string &user_id)
+{
+    std::map<std::string, RoomInfo> result;
+
+    auto txn = ro_txn(env_);
+
+    std::string_view room_id;
+    std::string_view room_data;
+    std::string_view member_info;
+
+    auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
+    while (roomsCursor.get(room_id, room_data, MDB_NEXT)) {
+        try {
+            if (getMembersDb(txn, std::string(room_id)).get(txn, user_id, member_info)) {
+                RoomInfo tmp = nlohmann::json::parse(std::move(room_data)).get<RoomInfo>();
+                result.emplace(std::string(room_id), std::move(tmp));
+            }
+        } catch (std::exception &e) {
+            nhlog::db()->warn("Failed to read common room for member ({}) in room ({}): {}",
+                              user_id,
+                              room_id,
+                              e.what());
+        }
+    }
+    roomsCursor.close();
+
+    return result;
+}
+
 std::optional<MemberInfo>
 Cache::getMember(const std::string &room_id, const std::string &user_id)
 {
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 5a4f9afb..38cadfc9 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -64,6 +64,7 @@ public:
     crypto::Trust roomVerificationStatus(const std::string &room_id);
 
     std::vector<std::string> joinedRooms();
+    std::map<std::string, RoomInfo> getCommonRooms(const std::string &user_id);
 
     QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
     QHash<QString, RoomInfo> invites();
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 66a68bb8..80def409 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -58,6 +58,12 @@ UserProfile::UserProfile(const QString &roomid,
           emit verificationStatiChanged();
       });
     fetchDeviceList(this->userid_);
+
+    if (userid != utils::localUser())
+        sharedRooms_ =
+          new RoomInfoModel(cache::client()->getCommonRooms(userid.toStdString()), this);
+    else
+        sharedRooms_ = new RoomInfoModel({}, this);
 }
 
 QHash<int, QByteArray>
@@ -102,12 +108,53 @@ DeviceInfoModel::reset(const std::vector<DeviceInfo> &deviceList)
     endResetModel();
 }
 
+RoomInfoModel::RoomInfoModel(const std::map<std::string, RoomInfo> &raw, QObject *parent)
+  : QAbstractListModel(parent)
+{
+    for (const auto &e : raw)
+        roomInfos_.push_back(e);
+}
+
+QHash<int, QByteArray>
+RoomInfoModel::roleNames() const
+{
+    return {
+      {RoomId, "roomId"},
+      {RoomName, "roomName"},
+      {AvatarUrl, "avatarUrl"},
+    };
+}
+
+QVariant
+RoomInfoModel::data(const QModelIndex &index, int role) const
+{
+    if (!index.isValid() || index.row() >= (int)roomInfos_.size() || index.row() < 0)
+        return {};
+
+    switch (role) {
+    case RoomId:
+        return QString::fromStdString(roomInfos_[index.row()].first);
+    case RoomName:
+        return QString::fromStdString(roomInfos_[index.row()].second.name);
+    case AvatarUrl:
+        return QString::fromStdString(roomInfos_[index.row()].second.avatar_url);
+    default:
+        return {};
+    }
+}
+
 DeviceInfoModel *
 UserProfile::deviceList()
 {
     return &this->deviceList_;
 }
 
+RoomInfoModel *
+UserProfile::sharedRooms()
+{
+    return this->sharedRooms_;
+}
+
 QString
 UserProfile::userid()
 {
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index d8423ffa..a880f320 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -119,6 +119,30 @@ private:
     friend class UserProfile;
 };
 
+class RoomInfoModel final : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    enum Roles
+    {
+        RoomId,
+        RoomName,
+        AvatarUrl,
+    };
+
+    explicit RoomInfoModel(const std::map<std::string, RoomInfo> &, QObject *parent = nullptr);
+    QHash<int, QByteArray> roleNames() const override;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override
+    {
+        (void)parent;
+        return (int)roomInfos_.size();
+    }
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+private:
+    std::vector<std::pair<std::string, RoomInfo>> roomInfos_;
+};
+
 class UserProfile final : public QObject
 {
     Q_OBJECT
@@ -126,6 +150,7 @@ class UserProfile final : public QObject
     Q_PROPERTY(QString userid READ userid CONSTANT)
     Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
     Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList NOTIFY devicesChanged)
+    Q_PROPERTY(RoomInfoModel *sharedRooms READ sharedRooms CONSTANT)
     Q_PROPERTY(bool isGlobalUserProfile READ isGlobalUserProfile CONSTANT)
     Q_PROPERTY(int userVerified READ getUserStatus NOTIFY userStatusChanged)
     Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
@@ -139,6 +164,7 @@ public:
                 TimelineModel *parent = nullptr);
 
     DeviceInfoModel *deviceList();
+    RoomInfoModel *sharedRooms();
 
     QString userid();
     QString displayName();
@@ -198,4 +224,5 @@ private:
     bool isLoading_              = false;
     TimelineViewManager *manager;
     TimelineModel *model;
+    RoomInfoModel *sharedRooms_ = nullptr;
 };