diff --git a/src/contents/ui/Notifier.qml b/src/contents/ui/Notifier.qml --- a/src/contents/ui/Notifier.qml +++ b/src/contents/ui/Notifier.qml @@ -18,6 +18,7 @@ } property var nameProvider: Kazv.UserNameProvider {} + property var roomNameProvider: Kazv.RoomNameProvider {} property var conn: Connections { target: matrixSdk @@ -35,7 +36,7 @@ roomId, }); notification.eventId = matrixSdk.shouldPlaySound(event) ? 'message' : 'messageWithoutSound'; - notification.title = Helpers.roomNameOrHeroes(room, l10n); + notification.title = roomNameProvider.getName(room); const message = event.content.body; notification.text = message ? l10n.get('notification-message', { user: senderName, message }) diff --git a/src/contents/ui/RoomListViewItemDelegate.qml b/src/contents/ui/RoomListViewItemDelegate.qml --- a/src/contents/ui/RoomListViewItemDelegate.qml +++ b/src/contents/ui/RoomListViewItemDelegate.qml @@ -22,7 +22,10 @@ property var item property var iconSize: 1 property var isFavourite: item.tagIds.includes('m.favourite') - property var roomDisplayName: Helpers.roomNameOrHeroes(item, l10n) + property var roomNameProvider: Kazv.RoomNameProvider { + room: upper.item + } + property var roomDisplayName: roomNameProvider.name property var roomTimeline: item.timeline() property var lastUnreadMessage: getLastUnreadMessage(roomTimeline, roomTimeline.count) property var lastUnreadMessageReaders: lastUnreadMessage ? lastUnreadMessage.readers() : null @@ -63,6 +66,7 @@ } Label { + objectName: 'roomDisplayNameLabel' text: roomDisplayName Layout.fillWidth: true elide: Text.ElideRight diff --git a/src/contents/ui/RoomNameProvider.qml b/src/contents/ui/RoomNameProvider.qml new file mode 100644 --- /dev/null +++ b/src/contents/ui/RoomNameProvider.qml @@ -0,0 +1,29 @@ +/* + * This file is part of kazv. + * SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 + +import 'matrix-helpers.js' as Helpers + +QtObject { + property var room + property var name: getName(room) + + function getName(r) { + if (!r) { + return ''; + } + + return Helpers.roomNameOrHeroes( + r, + r.heroEvents, + sdkVars.userGivenNicknameMap.map, + l10n, + ); + } +} diff --git a/src/contents/ui/RoomPage.qml b/src/contents/ui/RoomPage.qml --- a/src/contents/ui/RoomPage.qml +++ b/src/contents/ui/RoomPage.qml @@ -19,13 +19,16 @@ id: roomPage property string roomId: '' property var room: sdkVars.roomList.room(roomId) + property var roomNameProvider: Kazv.RoomNameProvider { + room: roomPage.room + } property var roomTimeline: room.timeline() property var lastReceiptableEventId: getLastReceiptableEventId(roomTimeline, roomTimeline.count) signal mentionUserRequested(string userId) signal replaceDraftRequested(string newDraft) - title: room.name || roomId + title: roomNameProvider.name property var isInvite: room.membership === MK.MatrixRoom.Invite property var isJoin: room.membership === MK.MatrixRoom.Join diff --git a/src/contents/ui/UserPage.qml b/src/contents/ui/UserPage.qml --- a/src/contents/ui/UserPage.qml +++ b/src/contents/ui/UserPage.qml @@ -24,7 +24,10 @@ user: userPage.user } property var room - property var roomName: Helpers.roomNameOrHeroes(room, l10n) + property var roomNameProvider: Kazv.RoomNameProvider { + room: userPage.room + } + property var roomName: roomNameProvider.name property var editingPowerLevel: false property var submittingPowerLevel: false property var powerLevelsLoaded: false diff --git a/src/contents/ui/room-settings/RoomInvitePage.qml b/src/contents/ui/room-settings/RoomInvitePage.qml --- a/src/contents/ui/room-settings/RoomInvitePage.qml +++ b/src/contents/ui/room-settings/RoomInvitePage.qml @@ -17,7 +17,10 @@ id: roomInvitePage property var room - property var roomDisplayName: Helpers.roomNameOrHeroes(room, l10n) + property var roomNameProvider: Kazv.RoomNameProvider { + room: roomInvitePage.room + } + property var roomDisplayName: roomNameProvider.name property var inviting: false title: l10n.get('room-invite-page-title', { room: roomDisplayName }) diff --git a/src/contents/ui/room-settings/RoomMemberListPage.qml b/src/contents/ui/room-settings/RoomMemberListPage.qml --- a/src/contents/ui/room-settings/RoomMemberListPage.qml +++ b/src/contents/ui/room-settings/RoomMemberListPage.qml @@ -19,7 +19,10 @@ property var room required property var members - property var roomDisplayName: Helpers.roomNameOrHeroes(room, l10n) + property var roomNameProvider: Kazv.RoomNameProvider { + room: roomSettingsPage.room + } + property var roomDisplayName: roomNameProvider.name title: l10n.get('room-member-list-page-title', { room: roomDisplayName }) Kazv.RoomMemberListView { 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 @@ -19,7 +19,10 @@ id: roomSettingsPage property var room - property var roomDisplayName: Helpers.roomNameOrHeroes(room, l10n) + property var roomNameProvider: Kazv.RoomNameProvider { + room: roomSettingsPage.room + } + property var roomDisplayName: roomNameProvider.name property var customTagIds: room.tagIds.filter(k => k.startsWith('u.')) title: l10n.get('room-settings-page-title', { room: roomDisplayName }) diff --git a/src/contents/ui/room-settings/RoomTagHandler.qml b/src/contents/ui/room-settings/RoomTagHandler.qml --- a/src/contents/ui/room-settings/RoomTagHandler.qml +++ b/src/contents/ui/room-settings/RoomTagHandler.qml @@ -15,8 +15,12 @@ import '..' as Kazv QtObject { + id: roomTagHandler property var room - property var roomDisplayName: Helpers.roomNameOrHeroes(room, l10n) + property var roomNameProvider: Kazv.RoomNameProvider { + room: roomTagHandler.room + } + property var roomDisplayName: roomNameProvider.name property var tagIds: room.tagIds property var available: true property var tagIdInProgress: '' diff --git a/src/js/matrix-helpers.js b/src/js/matrix-helpers.js --- a/src/js/matrix-helpers.js +++ b/src/js/matrix-helpers.js @@ -4,7 +4,7 @@ return room.name || room.heroNames[0] || room.roomId; } -function roomNameOrHeroes(room, l10n) +function roomNameOrHeroes(room, heroes, userNameOverrides, l10n) { if (room.name) { return l10n.get( @@ -13,13 +13,19 @@ name: room.name, } ); - } else if (room.heroNames.length) { + } else if (room.heroEvents && room.heroEvents.length) { + const heroNames = room.heroEvents.map(ev => { + const displayName = ev.content && ev.content.displayname || ''; + const userId = ev.state_key; + return userDisplayName(displayName, userId, userNameOverrides, l10n); + }); + return l10n.get( 'room-list-view-room-item-title-heroes', { - hero: room.heroNames[0], - secondHero: room.heroNames[1], - otherNum: room.heroNames.length - 1, + hero: heroNames[0], + secondHero: heroNames[1], + otherNum: heroNames.length - 1, } ); } else { diff --git a/src/matrix-room.hpp b/src/matrix-room.hpp --- a/src/matrix-room.hpp +++ b/src/matrix-room.hpp @@ -51,6 +51,7 @@ LAGER_QT_READER(QString, roomId); LAGER_QT_READER(QString, name); LAGER_QT_READER(QStringList, heroNames); + LAGER_QT_READER(QVariant, heroEvents); LAGER_QT_READER(QString, avatarMxcUri); LAGER_QT_READER(QString, roomOrHeroAvatarMxcUri); LAGER_QT_READER(QString, localDraft); diff --git a/src/matrix-room.cpp b/src/matrix-room.cpp --- a/src/matrix-room.cpp +++ b/src/matrix-room.cpp @@ -22,7 +22,7 @@ #include "matrix-promise.hpp" #include "matrix-event.hpp" #include "kazv-markdown.hpp" - +#include "qt-json.hpp" #include "qfunctionutils.hpp" #include "helper.hpp" @@ -42,6 +42,17 @@ , LAGER_QT(roomId)(m_room.roomId().xform(strToQt)) , LAGER_QT(name)(m_room.nameOpt()[lager::lenses::or_default].xform(strToQt)) , LAGER_QT(heroNames)(m_room.heroDisplayNames().xform(strListToQt)) + , LAGER_QT(heroEvents)(m_room.heroMemberEvents() + .map([](const auto &events) { + auto res = QList<QVariant>{}; + res.reserve(events.size()); + std::transform(events.begin(), events.end(), + std::back_inserter(res), + [](const auto &event) -> QVariant { + return event.originalJson().get().template get<QJsonObject>(); + }); + return QVariant(res); + })) , LAGER_QT(avatarMxcUri)(m_room.avatarMxcUri().xform(strToQt)) , LAGER_QT(roomOrHeroAvatarMxcUri)(lager::with(m_room.heroMemberEvents(), m_room.avatarMxcUri()) .map([](const auto &heroes, const auto &roomAvatar) { diff --git a/src/resources.qrc b/src/resources.qrc --- a/src/resources.qrc +++ b/src/resources.qrc @@ -49,6 +49,7 @@ <file alias="RoomMemberListView.qml">contents/ui/RoomMemberListView.qml</file> <file alias="RoomMemberListViewItemDelegate.qml">contents/ui/RoomMemberListViewItemDelegate.qml</file> <file alias="UserNameProvider.qml">contents/ui/UserNameProvider.qml</file> + <file alias="RoomNameProvider.qml">contents/ui/RoomNameProvider.qml</file> <file alias="AsyncHandler.qml">contents/ui/AsyncHandler.qml</file> <file alias="UploadFileHelper.qml">contents/ui/UploadFileHelper.qml</file> diff --git a/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml b/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml --- a/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml +++ b/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml @@ -121,6 +121,13 @@ property var matrixSdk: TestHelpers.MatrixSdkMock { property var userId: '@foo:example.org' } + property var sdkVars: QtObject { + property var userGivenNicknameMap: QtObject { + property var map: ({ + '@foo:example.com': 'something', + }) + } + } property string testRoomId: 'room-test' signal switchToRoomRequested(string roomId) @@ -153,6 +160,34 @@ roomId: testRoomId }) } + + Kazv.RoomListViewItemDelegate { + id: delegateRoomWithName + item: QtObject { + property var roomId: testRoomId + property var name: 'some name' + } + } + + Kazv.RoomListViewItemDelegate { + id: delegateRoomWithHeroes + item: QtObject { + property var roomId: testRoomId + property var heroEvents: [{ + type: 'm.room.member', + state_key: '@foo:example.com', + content: { + displayname: 'foo', + }, + }, { + type: 'm.room.member', + state_key: '@bar:example.com', + content: { + displayname: 'bar', + }, + }] + } + } } TestCase { @@ -204,5 +239,21 @@ tryCompare(switchToRoomSpy, 'count', 1); verify(Helpers.deepEqual(switchToRoomSpy.signalArguments[0], [testRoomId])); } + + function test_roomName() { + compare( + findChild(delegateRoomWithName, 'roomDisplayNameLabel').text, + l10n.get('room-list-view-room-item-title-name', { name: 'some name' })); + + compare( + findChild(delegateRoomWithHeroes, 'roomDisplayNameLabel').text, + l10n.get('room-list-view-room-item-title-heroes', { + hero: l10n.get( + 'user-name-overrided', + { overridedName: 'something', globalName: 'foo' }), + secondHero: 'bar', + otherNum: 1, + })); + } } } diff --git a/src/tests/quick-tests/tst_RoomNameProvider.qml b/src/tests/quick-tests/tst_RoomNameProvider.qml new file mode 100644 --- /dev/null +++ b/src/tests/quick-tests/tst_RoomNameProvider.qml @@ -0,0 +1,164 @@ +/* + * This file is part of kazv. + * SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtTest 1.0 + +import '../../contents/ui' as Kazv +import './test-helpers' as TestHelpers +import 'test-helpers.js' as Helpers + +TestCase { + id: roomNameProviderTest + name: 'RoomNameProviderTest' + when: true + + property var sdkVars: QtObject { + property var userGivenNicknameMap: QtObject { + property var map: ({}) + } + } + property var l10n: Helpers.fluentMock + + property var providerFunctional: Kazv.RoomNameProvider {} + property var providerReactive: Kazv.RoomNameProvider {} + property var roomComp: Component { + QtObject { + property var name + property var heroEvents + property var roomId + } + } + property var heroEvents: [{ + type: 'm.room.member', + state_key: '@foo:example.com', + content: { + displayname: 'foo', + }, + }, { + type: 'm.room.member', + state_key: '@bar:example.com', + content: { + displayname: 'bar', + }, + }] + + function init() { + sdkVars.userGivenNicknameMap.map = { + '@foo:example.com': 'something', + }; + } + + function test_functional() { + compare( + providerFunctional.getName({ name: 'some name' }), + l10n.get( + 'room-list-view-room-item-title-name', + { name: 'some name' })); + + compare(providerFunctional.getName({ + heroEvents + }), l10n.get('room-list-view-room-item-title-heroes', { + hero: l10n.get( + 'user-name-overrided', + { overridedName: 'something', globalName: 'foo' }), + secondHero: 'bar', + otherNum: 1, + })); + + compare( + providerFunctional.getName({ roomId: '!room:example.com' }), + l10n.get( + 'room-list-view-room-item-title-id', + { roomId: '!room:example.com' })); + } + + function test_reactiveAgainstRoom() { + providerReactive.room = roomComp.createObject(roomNameProviderTest, { + name: 'some name', + heroEvents, + roomId: '!room:example.com', + }); + compare( + providerReactive.name, + l10n.get( + 'room-list-view-room-item-title-name', + { name: 'some name' })); + providerReactive.room.name = 'other name'; + compare( + providerReactive.name, + l10n.get( + 'room-list-view-room-item-title-name', + { name: 'other name' })); + providerReactive.room.name = undefined; + compare( + providerReactive.name, + l10n.get('room-list-view-room-item-title-heroes', { + hero: l10n.get( + 'user-name-overrided', + { overridedName: 'something', globalName: 'foo' }), + secondHero: 'bar', + otherNum: 1, + })); + + providerReactive.room.heroEvents = [heroEvents[0]]; + compare( + providerReactive.name, + l10n.get('room-list-view-room-item-title-heroes', { + hero: l10n.get( + 'user-name-overrided', + { overridedName: 'something', globalName: 'foo' }), + secondHero: undefined, + otherNum: 0, + })); + + providerReactive.room.heroEvents = []; + compare( + providerReactive.name, + l10n.get('room-list-view-room-item-title-id', { + roomId: '!room:example.com', + })); + + providerReactive.room = roomComp.createObject(roomNameProviderTest, { + name: 'some name 2', + heroEvents, + roomId: '!room:example.com', + }); + compare( + providerReactive.name, + l10n.get( + 'room-list-view-room-item-title-name', + { name: 'some name 2' })); + } + + function test_reactiveAgainstOverrides() { + providerReactive.room = roomComp.createObject(roomNameProviderTest, { + heroEvents, + roomId: '!room:example.com', + }); + + compare( + providerReactive.name, + l10n.get('room-list-view-room-item-title-heroes', { + hero: l10n.get( + 'user-name-overrided', + { overridedName: 'something', globalName: 'foo' }), + secondHero: 'bar', + otherNum: 1, + })); + sdkVars.userGivenNicknameMap.map = { + }; + + compare( + providerReactive.name, + l10n.get('room-list-view-room-item-title-heroes', { + hero: 'foo', + secondHero: 'bar', + otherNum: 1, + })); + } +}