diff --git a/src/contents/ui/room-settings/RoomSettingsPage.qml b/src/contents/ui/room-settings/RoomSettingsPage.qml --- a/src/contents/ui/room-settings/RoomSettingsPage.qml +++ b/src/contents/ui/room-settings/RoomSettingsPage.qml @@ -26,6 +26,26 @@ } property var roomDisplayName: roomNameProvider.name property var customTagIds: room.tagIds.filter(k => k.startsWith('u.')) + property var editingTopic: false + property var submittingTopic: false + property var submitTopic: Kazv.AsyncHandler { + trigger: () => { + roomSettingsPage.submittingTopic = true; + return roomSettingsPage.room.setTopic(roomTopicEdit.text); + } + onResolved: (success, data) => { + roomSettingsPage.submittingTopic = false; + if (success) { + roomSettingsPage.editingTopic = false; + } else { + showPassiveNotification(l10n.get('room-settings-set-topic-failed-prompt', { + room: room.roomId, + errorCode: data.errorCode, + errorMsg: data.error, + })); + } + } + } title: l10n.get('room-settings-page-title', { room: roomDisplayName }) @@ -79,7 +99,7 @@ property var members: room.members() property var avatarCount: 4 - + ColumnLayout { RowLayout { Layout.alignment: Qt.AlignHCenter @@ -125,6 +145,49 @@ visible: !!room.name } } + RowLayout { + visible: !roomSettingsPage.editingTopic + Kazv.SelectableText { + objectName: 'roomTopicLabel' + text: room.topic || l10n.get('room-settings-topic-missing') + font.italic: !room.topic + wrapMode: Text.Wrap + Kirigami.Theme.colorGroup: !!room.topic ? Kirigami.Theme.Normal : Kirigami.Theme.Inactive + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignTop + objectName: 'editTopicButton' + text: l10n.get('room-settings-edit-topic-action') + onClicked: { + roomTopicEdit.text = room.topic; + roomSettingsPage.editingTopic = true; + } + } + } + RowLayout { + visible: roomSettingsPage.editingTopic + TextArea { + id: roomTopicEdit + objectName: 'roomTopicEdit' + Layout.fillWidth: true + wrapMode: Text.Wrap + } + Button { + Layout.alignment: Qt.AlignTop + objectName: 'saveTopicButton' + enabled: !roomSettingsPage.submittingTopic + text: l10n.get('room-settings-save-topic-action') + onClicked: roomSettingsPage.submitTopic.call() + } + Button { + Layout.alignment: Qt.AlignTop + objectName: 'discardTopicButton' + enabled: !roomSettingsPage.submittingTopic + text: l10n.get('room-settings-discard-topic-action') + onClicked: roomSettingsPage.editingTopic = false + } + } Button { text: l10n.get('room-settings-members-action') icon.name: 'im-user' diff --git a/src/l10n/cmn-Hans/100-ui.ftl b/src/l10n/cmn-Hans/100-ui.ftl --- a/src/l10n/cmn-Hans/100-ui.ftl +++ b/src/l10n/cmn-Hans/100-ui.ftl @@ -93,6 +93,11 @@ room-settings-not-encrypted = 本房间中的消息没有端对端加密。 room-settings-encryption-enabled-notification = 本房间中的加密已经启用。 room-settings-encryption-failed-to-enable-notification = 不能在本房间中启用加密。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。 +room-settings-topic-missing = 这个房间没有话题。 +room-settings-edit-topic-action = 编辑话题 +room-settings-save-topic-action = 保存话题 +room-settings-discard-topic-action = 放弃话题 +room-settings-set-topic-failed-prompt = 不能设置话题。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。 room-member-list-page-title = { $room } 的成员 diff --git a/src/l10n/en/100-ui.ftl b/src/l10n/en/100-ui.ftl --- a/src/l10n/en/100-ui.ftl +++ b/src/l10n/en/100-ui.ftl @@ -97,6 +97,11 @@ room-settings-not-encrypted = Messages in this room are not end-to-end-encrypted. room-settings-encryption-enabled-notification = Encryption is now enabled in this room. room-settings-encryption-failed-to-enable-notification = Cannot enable encryption in this room. Error code: { $errorCode }. Error message: { $errorMsg }. +room-settings-topic-missing = This room does not have a topic. +room-settings-edit-topic-action = Edit topic +room-settings-save-topic-action = Save topic +room-settings-discard-topic-action = Discard topic +room-settings-set-topic-failed-prompt = Cannot set topic. Error code: { $errorCode }. Error message: { $errorMsg }. room-member-list-page-title = Members of { $room } diff --git a/src/matrix-room.hpp b/src/matrix-room.hpp --- a/src/matrix-room.hpp +++ b/src/matrix-room.hpp @@ -49,6 +49,7 @@ LAGER_QT_READER(QString, roomId); LAGER_QT_READER(QString, name); + LAGER_QT_READER(QString, topic); LAGER_QT_READER(QStringList, heroNames); LAGER_QT_READER(QVariant, heroEvents); LAGER_QT_READER(QString, avatarMxcUri); @@ -134,6 +135,8 @@ Q_INVOKABLE MatrixPromise *postReadReceipt(const QString &eventId) const; + Q_INVOKABLE MatrixPromise *setTopic(const QString &newTopic) const; + Q_SIGNALS: void powerLevelsChanged(); diff --git a/src/matrix-room.cpp b/src/matrix-room.cpp --- a/src/matrix-room.cpp +++ b/src/matrix-room.cpp @@ -41,6 +41,7 @@ , m_powerLevels(m_room.powerLevels()) , LAGER_QT(roomId)(m_room.roomId().xform(strToQt)) , LAGER_QT(name)(m_room.nameOpt()[lager::lenses::or_default].xform(strToQt)) + , LAGER_QT(topic)(m_room.topic().xform(strToQt)) , LAGER_QT(heroNames)(m_room.heroDisplayNames().xform(strListToQt)) , LAGER_QT(heroEvents)(m_room.heroMemberEvents() .map([](const auto &events) { @@ -499,3 +500,8 @@ { return new MatrixPromise(m_room.postReceipt(eventId.toStdString())); } + +MatrixPromise *MatrixRoom::setTopic(const QString &newTopic) const +{ + return new MatrixPromise(m_room.setTopic(newTopic.toStdString())); +} diff --git a/src/tests/quick-tests/tst_RoomSettingsPage.qml b/src/tests/quick-tests/tst_RoomSettingsPage.qml --- a/src/tests/quick-tests/tst_RoomSettingsPage.qml +++ b/src/tests/quick-tests/tst_RoomSettingsPage.qml @@ -41,6 +41,7 @@ property var roomJoined: Helpers.factory.room({ membership: MK.MatrixRoom.Join, leaveRoom: mockHelper.promise(), + topic: '', }) property var roomWithMembers: Helpers.factory.room({ @@ -62,6 +63,12 @@ roomId: '!someid:example.com', }) + property var roomWithTopic: Helpers.factory.room({ + membership: MK.MatrixRoom.Join, + topic: 'some topic', + setTopic: mockHelper.promise(), + }) + property var showPassiveNotification: mockHelper.noop() property var l10n: Helpers.fluentMock @@ -100,6 +107,11 @@ id: pageWithoutName room: item.roomWithoutName } + + KazvRS.RoomSettingsPage { + id: pageWithTopic + room: item.roomWithTopic + } } TestCase { @@ -107,7 +119,16 @@ name: 'RoomSettingsPageTest' when: windowShown + function initTestCase() { + pageJoined.contentItem.clip = false; + pageWithTopic.contentItem.clip = false; + } + function init() { + pageJoined.submittingTopic = false; + pageJoined.editingTopic = false; + pageWithTopic.submittingTopic = false; + pageWithTopic.editingTopic = false; mockHelper.clearAll(); } @@ -188,6 +209,80 @@ verify(nameLabel.text === '!someid:example.com'); verify(!idLabel.visible) } - + + function test_roomWithoutTopic() { + const topicLabel = findChild(pageJoined, 'roomTopicLabel'); + verify(topicLabel.font.italic); + compare(topicLabel.text, l10n.get('room-settings-topic-missing')); + } + + function test_roomWithTopic() { + const topicLabel = findChild(pageWithTopic, 'roomTopicLabel'); + verify(!topicLabel.font.italic); + compare(topicLabel.text, 'some topic'); + } + + function test_editRoomTopic() { + const editButton = findChild(pageWithTopic, 'editTopicButton'); + mouseClick(editButton); + const textArea = findChild(pageWithTopic, 'roomTopicEdit'); + tryVerify(() => textArea.visible); + verify(textArea.text === 'some topic'); + textArea.text = 'other topic'; + const saveTopicButton = findChild(pageWithTopic, 'saveTopicButton'); + verify(saveTopicButton.visible); + verify(saveTopicButton.enabled); + const discardTopicButton = findChild(pageWithTopic, 'discardTopicButton'); + verify(discardTopicButton.visible); + verify(discardTopicButton.enabled); + mouseClick(saveTopicButton); + tryVerify(() => roomWithTopic.setTopic.calledTimes() === 1); + compare(roomWithTopic.setTopic.lastArgs()[0], 'other topic'); + verify(!saveTopicButton.enabled); + verify(!discardTopicButton.enabled); + + roomWithTopic.setTopic.lastRetVal().resolve(true, {}); + tryVerify(() => editButton.visible); + verify(!saveTopicButton.visible); + } + + function test_editRoomTopicFailed() { + const editButton = findChild(pageWithTopic, 'editTopicButton'); + mouseClick(editButton); + const textArea = findChild(pageWithTopic, 'roomTopicEdit'); + tryVerify(() => textArea.visible); + verify(textArea.text === 'some topic'); + textArea.text = 'other topic'; + const saveTopicButton = findChild(pageWithTopic, 'saveTopicButton'); + mouseClick(saveTopicButton); + tryVerify(() => roomWithTopic.setTopic.calledTimes() === 1); + roomWithTopic.setTopic.lastRetVal().resolve(false, {}); + verify(saveTopicButton.enabled); + verify(textArea.visible); + } + + function test_editRoomTopicDiscard() { + const editButton = findChild(pageWithTopic, 'editTopicButton'); + mouseClick(editButton); + const textArea = findChild(pageWithTopic, 'roomTopicEdit'); + tryVerify(() => textArea.visible); + verify(textArea.text === 'some topic'); + textArea.text = 'other topic'; + const discardTopicButton = findChild(pageWithTopic, 'discardTopicButton'); + mouseClick(discardTopicButton); + compare(roomWithTopic.setTopic.calledTimes(), 0); + verify(!textArea.visible); + mouseClick(editButton); + tryVerify(() => textArea.visible); + verify(textArea.text === 'some topic'); + } + + function test_addRoomTopic() { + const editButton = findChild(pageJoined, 'editTopicButton'); + mouseClick(editButton); + const textArea = findChild(pageJoined, 'roomTopicEdit'); + tryVerify(() => textArea.visible); + verify(textArea.text === ''); + } } }