Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85630397
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 309550d..3d4c747 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,113 +1,113 @@
cmake_minimum_required(VERSION 3.16)
project(kazv)
include(FeatureSummary)
set(kazv_VERSION_MAJOR 0)
set(kazv_VERSION_MINOR 6)
set(kazv_VERSION_PATCH 0)
set(kazv_VERSION_STRING ${kazv_VERSION_MAJOR}.${kazv_VERSION_MINOR}.${kazv_VERSION_PATCH})
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
set(CMAKE_CXX_STANDARD 20)
if(NOT kazv_KF_QT_MAJOR_VERSION)
set(kazv_KF_QT_MAJOR_VERSION 6)
endif()
message(STATUS "KF/Qt major version: ${kazv_KF_QT_MAJOR_VERSION}")
if(NOT kazv_KF_QT_MAJOR_VERSION EQUAL 6)
message(FATAL_ERROR "KF/Qt major version must be 6")
endif()
set(QT_MAJOR_VERSION ${kazv_KF_QT_MAJOR_VERSION})
set(KF_MAJOR_VERSION ${kazv_KF_QT_MAJOR_VERSION})
-set(QT_MIN_VERSION 6.5.0)
+set(QT_MIN_VERSION 6.9.0)
set(KF_MIN_VERSION 6.0.0)
option(kazv_LINK_BREEZE_ICONS "Link to Breeze icons library" OFF)
option(kazv_BUILD_TESTS "Build tests" ON)
option(kazv_ENABLE_APPIUM_TESTS "Run appium tests" "")
set(kazv_LINK_BREEZE_ICONS_MACRO 0)
if(kazv_LINK_BREEZE_ICONS)
set(kazv_LINK_BREEZE_ICONS_MACRO 1)
endif()
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
set(KDE_SKIP_NULLPTR_WARNINGS_SETTINGS ON)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMSetupVersion)
include(ECMGenerateHeaders)
include(ECMPoQmTools)
include(ECMAddTests)
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
include(ECMQmlModule)
if(ANDROID)
find_package(Perl 5.12 REQUIRED)
include(ECMAddAndroidApk)
endif()
ecm_set_disabled_deprecation_versions(
QT 5.15.14
KF 5.116.0
KCOREADDONS 5.116.0
)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} REQUIRED COMPONENTS
Core Gui Qml QuickControls2 Svg Concurrent Widgets
Multimedia Test Network QuickTest HttpServer Sql
)
qt6_policy(SET QTP0001 NEW)
find_package(QCoro6 REQUIRED COMPONENTS Core)
qcoro_enable_coroutines()
set(kazv_KF_EXTRA_MODULES)
if(kazv_LINK_BREEZE_ICONS)
list(APPEND kazv_KF_EXTRA_MODULES BreezeIcons)
endif()
find_package(KF${KF_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS Kirigami Config Notifications CoreAddons ${kazv_KF_EXTRA_MODULES})
find_package(KF6KirigamiAddons)
find_package(Threads REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(libkazv REQUIRED)
find_package(cmark)
set(CMARK_TARGET_NAME cmark::cmark)
if(NOT cmark_FOUND)
# CMark 0.29.0 on Ubuntu 20.04 has cmark.cmake instead of cmark-config.cmake
# But that is not usable, so we fall back to pkgconfig
find_package(PkgConfig REQUIRED)
pkg_search_module(cmark libcmark IMPORTED_TARGET GLOBAL)
set(CMARK_TARGET_NAME cmark)
endif()
if(ANDROID)
find_package(OpenSSL REQUIRED)
endif()
if(${kazv_ENABLE_APPIUM_TESTS} STREQUAL "")
find_package(SeleniumWebDriverATSPI)
if(SeleniumWebDriverATSPI_FOUND)
message(STATUS "Enabling appium tests because selenium-webdriver-at-spi is found. To turn it off, explicitly set kazv_ENABLE_APPIUM_TESTS to OFF")
set(kazv_ENABLE_APPIUM_TESTS ON)
endif()
elseif(kazv_ENABLE_APPIUM_TESTS)
find_package(SeleniumWebDriverATSPI REQUIRED)
endif()
kde_enable_exceptions()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -foperator-names -DQT_NO_EMIT")
add_subdirectory(src)
install(PROGRAMS moe.kazv.mxc.kazv.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES moe.kazv.mxc.kazv.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
install(FILES kazv.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
install(FILES icons/kazvlogo-256.png DESTINATION ${KDE_INSTALL_ICONDIR}/hicolor/256x256/apps RENAME moe.kazv.mxc.kazv.png)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/src/contents/ui/RoomListView.qml b/src/contents/ui/RoomListView.qml
index f552e4c..f376472 100644
--- a/src/contents/ui/RoomListView.qml
+++ b/src/contents/ui/RoomListView.qml
@@ -1,36 +1,37 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.2
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.13 as Kirigami
import 'matrix-helpers.js' as Helpers
import '.' as Kazv
import 'room-settings' as RoomSettings
ListView {
id: roomListView
property var roomList
property var roomIds: roomList.roomIds
property var iconSize: Kirigami.Units.iconSizes.large
Layout.fillHeight: true
model: roomList
Layout.minimumHeight: childrenRect.height
currentIndex: roomIds.indexOf(sdkVars.currentRoomId)
delegate: Kazv.RoomListViewItemDelegate {
required property var index
+ width: ListView.view.width
item: roomList.at(index)
iconSize: roomListView.iconSize
}
}
diff --git a/src/contents/ui/RoomListViewItemDelegate.qml b/src/contents/ui/RoomListViewItemDelegate.qml
index 0cb87a7..897549f 100644
--- a/src/contents/ui/RoomListViewItemDelegate.qml
+++ b/src/contents/ui/RoomListViewItemDelegate.qml
@@ -1,174 +1,179 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.2
import QtQuick.Layouts 1.15
-import QtQuick.Controls 2.15
+import QtQuick.Controls
import org.kde.kirigami 2.13 as Kirigami
import moe.kazv.mxc.kazv 0.0 as MK
import 'matrix-helpers.js' as Helpers
import 'room-settings' as RoomSettings
import '.' as Kazv
-Kirigami.SwipeListItem {
+ItemDelegate {
id: upper
property var item
property var iconSize: 1
property var isFavourite: item.tagIds.includes('m.favourite')
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, item)
property var lastUnreadMessageReaders: lastUnreadMessage ? lastUnreadMessage.readers() : null
// The readers count is to make hasUnreadMessages prop reactive
// against our newly-posted receipts
property var hasUnreadMessages: getHasUnreadMessages(item, roomTimeline, roomTimeline.count, lastUnreadMessageReaders ? lastUnreadMessageReaders.count : 0)
onClicked: switchToRoomRequested(item.roomId)
checkable: true
checked: sdkVars.currentRoomId == item.roomId
autoExclusive: true
+ highlighted: checked
Kirigami.Theme.colorSet: !checked ? Kirigami.Theme.View : Kirigami.Theme.Selection
Kirigami.Theme.inherit: false
focusPolicy: Qt.StrongFocus
Accessible.focusable: true
Accessible.name: roomDisplayName
- background: Rectangle {
- anchors.fill: parent
- color: (upper.hovered || upper.visualFocus) ? Kirigami.Theme.hoverColor : Kirigami.Theme.backgroundColor
- }
contentItem: RowLayout {
Kazv.AvatarAdapter {
mxcUri: item.roomOrHeroAvatarMxcUri
name: Helpers.roomAvatarPlaceholderName(item)
}
+ Kirigami.Icon {
+ objectName: 'favIndicator'
+ source: /* iconName: */ 'starred-symbolic'
+ visible: upper.isFavourite
+ Accessible.role: Accessible.Indicator
+ Accessible.name: l10n.get('room-list-view-room-item-fav-indicator')
+ }
+
Label {
objectName: 'tombstoneIndicator'
visible: item.tombstoned === true
text: l10n.get('room-list-view-room-item-tombstone')
background: Rectangle {
color: Kirigami.Theme.negativeBackgroundColor
}
color: Kirigami.Theme.negativeTextColor
}
Label {
objectName: 'inviteIndicator'
visible: item.membership === MK.MatrixRoom.Invite
text: l10n.get('room-list-view-room-item-invited')
background: Rectangle {
color: Kirigami.Theme.activeBackgroundColor
}
color: Kirigami.Theme.activeTextColor
}
Label {
objectName: 'leaveIndicator'
visible: item.membership === MK.MatrixRoom.Leave
text: l10n.get('room-list-view-room-item-left')
color: Kirigami.Theme.disabledTextColor
}
Label {
objectName: 'roomDisplayNameLabel'
text: roomDisplayName
Layout.fillWidth: true
elide: Text.ElideRight
color: item.membership === MK.MatrixRoom.Leave ? Kirigami.Theme.disabledTextColor : Kirigami.Theme.textColor
}
Rectangle {
objectName: 'unreadNotificationCount'
Layout.alignment: Qt.AlignRight
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.preferredWidth: Math.max(unreadNotificationCountLabel.implicitWidth, Kirigami.Units.gridUnit)
Layout.preferredHeight: Kirigami.Units.gridUnit
radius: Kirigami.Units.gridUnit / 2
color: Kirigami.Theme.activeBackgroundColor
visible: !!item.unreadNotificationCount
RowLayout {
anchors.fill: parent
Label {
id: unreadNotificationCountLabel
Layout.alignment: Qt.AlignHCenter
color: Kirigami.Theme.activeTextColor
text: l10n.get('room-list-view-room-item-unread-notification-count', { count: item.unreadNotificationCount })
Accessible.ignored: true
}
}
Accessible.role: Accessible.Indicator
Accessible.name: l10n.get('room-list-view-room-item-unread-notification-count-text', { count: item.unreadNotificationCount })
}
Rectangle {
objectName: 'unreadIndicator'
Layout.alignment: Qt.AlignRight
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
radius: Kirigami.Units.iconSizes.small / 2
color: Kirigami.Theme.activeTextColor
visible: !item.unreadNotificationCount && hasUnreadMessages
Accessible.role: Accessible.Indicator
Accessible.name: l10n.get('room-list-view-room-item-unread-indicator')
}
}
- actions: [
+ ContextMenu.menu: Menu {
Kirigami.Action {
text: l10n.get("room-list-view-room-item-fav-action")
checkable: true
checked: isFavourite
icon.name: isFavourite ? 'starred-symbolic' : 'non-starred-symbolic'
onTriggered: toggleFavourite()
- },
+ }
Kirigami.Action {
text: l10n.get('room-list-view-room-item-more-action')
icon.name: 'view-more-horizontal-symbolic'
onTriggered: activateRoomSettingsPage(item)
}
- ]
+ }
function toggleFavourite() {
favouriteToggler.toggleTag('m.favourite');
}
property var favouriteToggler: RoomSettings.RoomTagHandler {
room: item
}
function getHasUnreadMessages(room, timeline, timelineCount, readersCount) {
return !!room.unreadNotificationCount || !!getLastUnreadMessage(timeline, timelineCount, room);
}
function getLastUnreadMessage(timeline, timelineCount, room) {
for (let i = 0; i < timelineCount; ++i) {
const event = timeline.at(i);
if (event.isLocalEcho) {
// Skip local echoes
} else if (room.localReadMarker === event.eventId) {
// This message is read locally
return undefined;
} else if (event.sender === matrixSession.userId) {
// Own message here
return undefined;
} else if (Helpers.isEventReadBy(event, matrixSession.userId)) {
// This message is read
return undefined;
} else {
// The latest message is not read and not sent by the current user
return event;
}
}
}
}
diff --git a/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml b/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml
index ad99a7d..d8a133f 100644
--- a/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml
+++ b/src/tests/quick-tests/tst_RoomListViewItemDelegate.qml
@@ -1,313 +1,337 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.3
import QtQuick.Layouts 1.15
import QtTest 1.0
import '../../contents/ui' as Kazv
import 'test-helpers.js' as JsHelpers
import 'test-helpers' as QmlHelpers
import moe.kazv.mxc.kazv 0.0 as MK
QmlHelpers.TestItem {
id: item
property var roomInvite: JsHelpers.factory.room({
membership: MK.MatrixRoom.Invite,
unreadNotificationCount: 0,
tombstoned: true,
})
property var roomJoin: JsHelpers.factory.room({
membership: MK.MatrixRoom.Join,
unreadNotificationCount: 0,
})
property var roomLeave: JsHelpers.factory.room({
membership: MK.MatrixRoom.Leave,
unreadNotificationCount: 0,
})
property var roomWithUnreadNotificationCount: JsHelpers.factory.room({
membership: MK.MatrixRoom.Join,
unreadNotificationCount: 2,
})
property var roomTombstone: JsHelpers.factory.room({
tombstoned: true,
unreadNotificationCount: 0,
})
+ property var roomFav: JsHelpers.factory.room({
+ membership: MK.MatrixRoom.Join,
+ unreadNotificationCount: 0,
+ tagIds: ['m.favourite'],
+ })
+
property var timelineUnread: QtObject {
property var count: 5
function at(i) {
if (i < 2) {
return {
isLocalEcho: true,
readers: () => ({ count: 0 }),
};
}
if (i < 3) {
return {
isLocalEcho: false,
sender: '@bar:example.org',
eventId: `$event${i}`,
readers: () => ({ count: 0 }),
};
}
return {
isLocalEcho: false,
sender: '@foo:example.org',
eventId: `$event${i}`,
readers: () => ({ count: 0 }),
};
}
}
property var timelineLastSentIsMine: QtObject {
property var count: 5
function at(i) {
if (i < 2) {
return {
isLocalEcho: true,
readers: () => ({ count: 0 }),
};
}
if (i < 3) {
return {
isLocalEcho: false,
sender: '@foo:example.org',
eventId: `$event${i}`,
readers: () => ({ count: 0 }),
};
}
return {
isLocalEcho: false,
sender: '@bar:example.org',
eventId: `$event${i}`,
readers: () => ({ count: 0 }),
};
}
}
property var timelineRead: QtObject {
property var count: 5
function at(i) {
if (i < 2) {
return {
isLocalEcho: true,
readers: () => ({ count: 0 }),
};
}
if (i < 3) {
return {
isLocalEcho: false,
sender: '@bar:example.org',
eventId: `$event${i}`,
readers: () => ({
count: 1,
at() {
return { userId: '@foo:example.org' };
},
}),
};
}
return {
isLocalEcho: false,
sender: '@foo:example.org',
eventId: `$event${i}`,
readers: () => ({ count: 0 }),
};
}
}
property string testRoomId: 'room-test'
signal switchToRoomRequested(string roomId)
SignalSpy {
id: switchToRoomSpy
target: item
signalName: 'switchToRoomRequested'
}
ColumnLayout {
anchors.fill: parent
Kazv.RoomListViewItemDelegate {
id: delegateInvite
item: roomInvite
}
Kazv.RoomListViewItemDelegate {
id: delegateJoin
item: roomJoin
}
Kazv.RoomListViewItemDelegate {
id: delegateLeave
item: roomLeave
}
Kazv.RoomListViewItemDelegate {
id: delegateRoom
item: JsHelpers.factory.room({
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',
},
}]
}
}
Kazv.RoomListViewItemDelegate {
id: delegateUnread
item: Object.assign({}, roomJoin, {
timeline () {
return timelineUnread;
},
})
}
Kazv.RoomListViewItemDelegate {
id: delegateWithUnreadNotifications
item: Object.assign({}, roomWithUnreadNotificationCount, {
timeline () {
return timelineUnread;
},
})
}
Kazv.RoomListViewItemDelegate {
id: delegateTombstone
item: roomTombstone
}
+
+ Kazv.RoomListViewItemDelegate {
+ id: delegateFav
+ item: roomFav
+ }
}
TestCase {
id: roomListViewItemDelegateTest
name: 'RoomListViewItemDelegateTest'
when: windowShown
function initTestCase() {
item.matrixSession.userId = '@foo:example.org';
item.sdkVars.userGivenNicknameMap.map = {
'@foo:example.com': 'something',
};
}
function test_inviteIndicator() {
const indicator = findChild(delegateInvite, 'inviteIndicator');
verify(indicator);
verify(indicator.visible);
const leaveIndicator = findChild(delegateInvite, 'leaveIndicator');
verify(leaveIndicator);
verify(!leaveIndicator.visible);
}
function test_inviteIndicatorJoin() {
const indicator = findChild(delegateJoin, 'inviteIndicator');
verify(indicator);
verify(!indicator.visible);
const leaveIndicator = findChild(delegateJoin, 'leaveIndicator');
verify(leaveIndicator);
verify(!leaveIndicator.visible);
}
function test_inviteIndicatorLeave() {
const indicator = findChild(delegateLeave, 'inviteIndicator');
verify(indicator);
verify(!indicator.visible);
const leaveIndicator = findChild(delegateLeave, 'leaveIndicator');
verify(leaveIndicator);
verify(leaveIndicator.visible);
}
+ function test_favIndicator() {
+ {
+ const indicator = findChild(delegateJoin, 'favIndicator');
+ verify(indicator);
+ verify(!indicator.visible);
+ }
+ {
+ const indicator = findChild(delegateFav, 'favIndicator');
+ verify(indicator);
+ verify(indicator.visible);
+ }
+ }
+
function test_lastUnreadMessage() {
const room = {};
compare(delegateJoin.getLastUnreadMessage(timelineUnread, timelineUnread.count, room).eventId, '$event2');
compare(delegateJoin.getLastUnreadMessage(timelineLastSentIsMine, timelineLastSentIsMine.count, room), undefined);
compare(delegateJoin.getLastUnreadMessage(timelineRead, timelineRead.count, room), undefined);
room.localReadMarker = '$event2';
compare(delegateJoin.getLastUnreadMessage(timelineUnread, timelineUnread.count, room), undefined);
}
function test_returnRoom() {
compare(switchToRoomSpy.count, 0);
mouseClick(delegateRoom);
tryCompare(switchToRoomSpy, 'count', 1);
verify(JsHelpers.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,
}));
}
function test_unreadIndicators() {
verify(!findChild(delegateWithUnreadNotifications, 'unreadIndicator').visible);
verify(findChild(delegateWithUnreadNotifications, 'unreadNotificationCount').visible);
verify(findChild(delegateUnread, 'unreadIndicator').visible);
verify(!findChild(delegateUnread, 'unreadNotificationCount').visible);
}
function test_tombstoneIndictor() {
const indicator = findChild(delegateTombstone, 'tombstoneIndicator');
verify(indicator);
verify(indicator.visible);
const indicatorJoin = findChild(delegateJoin, 'tombstoneIndicator');
verify(indicatorJoin);
verify(!indicatorJoin.visible);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Aug 9, 1:16 PM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1724584
Default Alt Text
(20 KB)
Attached To
Mode
rK kazv
Attached
Detach File
Event Timeline
Log In to Comment