Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
39 KB
Referenced Files
None
Subscribers
None
diff --git a/src/contents/ui/AddStickerPopup.qml b/src/contents/ui/AddStickerPopup.qml
index e7ffa42..3e6e024 100644
--- a/src/contents/ui/AddStickerPopup.qml
+++ b/src/contents/ui/AddStickerPopup.qml
@@ -1,88 +1,88 @@
/*
* 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 org.kde.kirigami 2.20 as Kirigami
import moe.kazv.mxc.kazv 0.0 as MK
import '.' as Kazv
-Kirigami.OverlaySheet {
+Kazv.SelfDestroyableOverlaySheet {
id: addStickerPopup
property var stickerPackList
property var event
property var currentPack: stickerPackList.at(0)
property var stickerSize: Kirigami.Units.iconSizes.enormous
property var addingSticker: false
title: l10n.get('add-sticker-popup-title')
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
Kirigami.Icon {
Layout.preferredHeight: addStickerPopup.stickerSize
Layout.preferredWidth: addStickerPopup.stickerSize
height: addStickerPopup.stickerSize
width: addStickerPopup.stickerSize
source: matrixSdk.mxcUriToHttp(event.content.url)
}
TextField {
Layout.fillWidth: true
id: shortCodeInput
objectName: 'shortCodeInput'
readOnly: addStickerPopup.addingSticker
Kirigami.FormData.label: l10n.get('add-sticker-popup-short-code-prompt')
}
Label {
objectName: 'shortCodeExistsWarning'
visible: currentPack.hasShortCode(shortCodeInput.text)
text: l10n.get('add-sticker-popup-short-code-exists-warning')
}
}
RowLayout {
Layout.alignment: Qt.AlignRight
Button {
objectName: 'addStickerButton'
text: l10n.get('add-sticker-popup-add-sticker-button')
onClicked: addStickerPopup.updateStickerPack.call()
enabled: !addStickerPopup.addingSticker
}
Button {
objectName: 'cancelButton'
text: l10n.get('add-sticker-popup-cancel-button')
onClicked: addStickerPopup.close()
}
}
}
property var updateStickerPack: Kazv.AsyncHandler {
trigger: () => {
addStickerPopup.addingSticker = true;
return matrixSdk.updateStickerPack(
currentPack.addSticker(shortCodeInput.text, addStickerPopup.event)
);
}
onResolved: (success, data) => {
addStickerPopup.addingSticker = false;
if (!success) {
showPassiveNotification(l10n.get('add-sticker-popup-failed-prompt', { errorCode: data.errorCode, errorMsg: data.error }));
} else {
addStickerPopup.close();
}
}
}
}
diff --git a/src/contents/ui/Bubble.qml b/src/contents/ui/Bubble.qml
index a4e4353..248053f 100644
--- a/src/contents/ui/Bubble.qml
+++ b/src/contents/ui/Bubble.qml
@@ -1,234 +1,244 @@
/*
* 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.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.13 as Kirigami
import '.' as Kazv
import 'matrix-helpers.js' as Helpers
ItemDelegate {
id: upper
+ objectName: 'bubble'
default property var children
property var currentEvent: event
property var menuContent: []
readonly property var bubbleSpacing: leftPadding + rightPadding
Layout.fillWidth: true
Layout.preferredHeight: bubbleRootLayout.implicitHeight
property var backgroundItem: Rectangle {
anchors.fill: parent
color: (hovered || isSelected) ? Kirigami.Theme.activeBackgroundColor : Kirigami.Theme.backgroundColor
}
background: compactMode ? null : backgroundItem
- property var eventSourcePopup: Kirigami.OverlaySheet {
- title: l10n.get('event-source-popup-title')
+ property var eventSourcePopupComp: Component {
+ Kazv.SelfDestroyableOverlaySheet {
+ shouldSelfDestroy: true
+ title: l10n.get('event-source-popup-title')
- Kazv.EventSourceView {
- event: upper.currentEvent
+ Kazv.EventSourceView {
+ event: upper.currentEvent
+ }
}
}
- property var reactionPopup: Kazv.ReactToEventPopup {
- onAccepted: eventView.reactWith(text)
+ property var reactionPopupComp: Component {
+ Kazv.ReactToEventPopup {
+ shouldSelfDestroy: true
+ onAccepted: eventView.reactWith(text)
+ }
}
function getIsEditable(event) {
return event.sender === matrixSdk.userId
&& event.type === 'm.room.message'
&& event.content.msgtype === 'm.text';
}
- property var menu: Menu {
- objectName: 'bubbleContextMenu'
- property list<QtObject> staticItems: [
- Kirigami.Action {
- objectName: 'replyToMenuItem'
- text: l10n.get('event-reply-action')
- onTriggered: setDraftRelation('m.in_reply_to', currentEvent.eventId)
- enabled: event && !event.redacted
- },
- Kirigami.Action {
- objectName: 'editMenuItem'
- text: l10n.get('event-edit-action')
- onTriggered: {
- setDraftRelation('m.replace', currentEvent.eventId);
- replaceDraftRequested(Helpers.getEventBodyForEditing(event));
+ property var menuComp: Component {
+ Menu {
+ objectName: 'bubbleContextMenu'
+ property list<QtObject> staticItems: [
+ Kirigami.Action {
+ objectName: 'replyToMenuItem'
+ text: l10n.get('event-reply-action')
+ onTriggered: setDraftRelation('m.in_reply_to', currentEvent.eventId)
+ enabled: event && !event.redacted
+ },
+ Kirigami.Action {
+ objectName: 'editMenuItem'
+ text: l10n.get('event-edit-action')
+ onTriggered: {
+ setDraftRelation('m.replace', currentEvent.eventId);
+ replaceDraftRequested(Helpers.getEventBodyForEditing(event));
+ }
+ enabled: event && !event.redacted && !event.isLocalEcho && getIsEditable(event)
+ },
+ Kirigami.Action {
+ objectName: 'deleteMenuItem'
+ text: l10n.get('event-delete')
+ onTriggered: eventView.redactSelf()
+ enabled: event && !event.redacted
+ },
+ Kirigami.Action {
+ objectName: 'reactMenuItem'
+ text: l10n.get('event-react-action')
+ onTriggered: reactionPopupComp.createObject(applicationWindow().overlay).open()
+ enabled: event && !event.redacted
+ },
+ MenuSeparator {},
+ Kirigami.Action {
+ text: l10n.get('event-view-source')
+ onTriggered: eventSourcePopupComp.createObject(applicationWindow().overlay).open()
}
- enabled: event && !event.redacted && !event.isLocalEcho && getIsEditable(event)
- },
- Kirigami.Action {
- objectName: 'deleteMenuItem'
- text: l10n.get('event-delete')
- onTriggered: eventView.redactSelf()
- enabled: event && !event.redacted
- },
- Kirigami.Action {
- objectName: 'reactMenuItem'
- text: l10n.get('event-react-action')
- onTriggered: reactionPopup.open()
- enabled: event && !event.redacted
- },
- MenuSeparator {},
- Kirigami.Action {
- text: l10n.get('event-view-source')
- onTriggered: eventSourcePopup.open()
- }
- ]
+ ]
- contentData: Array.from(staticItems).concat(upper.menuContent)
+ contentData: Array.from(staticItems).concat(upper.menuContent)
+ onClosed: destroy()
+ }
}
function shouldPopupMenu() {
return !compactMode
}
function maybePopupMenu() {
if (shouldPopupMenu()) {
- menu.popup(parent)
+ menuComp.createObject(parent).popup(parent)
}
}
RowLayout {
id: bubbleRootLayout
anchors.left: parent.left
anchors.right: parent.right
property var replyToOrAnnotatedEventId: currentEvent.replyingToEventId || (currentEvent.relationType === 'm.annotation' ? currentEvent.relatedEventId : '')
ColumnLayout {
Layout.fillWidth: true
property var inReplyTo: MouseArea {
Layout.fillWidth: true
Layout.margins: 0
Layout.minimumHeight: Math.min(inReplyToLayout.implicitHeight, Kirigami.Units.gridUnit * 5)
Layout.maximumHeight: Kirigami.Units.gridUnit * 5
clip: true
onClicked: eventListView.goToEvent(bubbleRootLayout.replyToOrAnnotatedEventId)
RowLayout {
id: inReplyToLayout
anchors {
top: parent.top
left: parent.left
right: parent.right
}
Rectangle {
Layout.fillHeight: true
Layout.preferredWidth: Kirigami.Units.smallSpacing
color: Kirigami.Theme.activeTextColor
clip: true
}
Loader {
id: inReplyToLoader
clip: true
source: 'qrc:/EventViewCompact.qml'
active: bubbleRootLayout.replyToOrAnnotatedEventId && !compactMode
asynchronous: true
Layout.minimumHeight: inReplyToLoader.item ? inReplyToLoader.item.height : 0
Layout.fillWidth: true
property var event: room.messageById(bubbleRootLayout.replyToOrAnnotatedEventId)
property var props: ({
event,
sender: room.member(event.sender || matrixSdk.userId),
stateKeyUser: event.stateKey ? room.member(event.stateKey) : {},
isGapped: timeline.gaps.includes(event.eventId),
})
}
}
}
children: [
...(bubbleRootLayout.replyToOrAnnotatedEventId && !compactMode ? [inReplyTo] : []),
...(Array.isArray(upper.children) ? upper.children :
upper.children ? [upper.children] : []),
]
}
property var popupAction: Kirigami.Action {
id: popupAction
text: l10n.get('event-popup-action')
iconName: 'view-more-horizontal-symbolic'
onTriggered: maybePopupMenu()
}
RowLayout {
Layout.alignment: Qt.AlignBottom
Kirigami.Icon {
source: 'emblem-encrypted-locked'
visible: !compactMode && event && event.encrypted
Layout.preferredHeight: inlineBadgeSize
Layout.preferredWidth: inlineBadgeSize
Accessible.role: Accessible.StaticText
Accessible.name: l10n.get('event-encrypted')
ToolTip.text: l10n.get('event-encrypted')
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hoverHandlerEncIcon.hovered
HoverHandler { id: hoverHandlerEncIcon }
}
Kirigami.Icon {
objectName: 'localEchoIndicator'
visible: !!event.isLocalEcho
source: event.isSending ? 'state-sync' : 'state-warning'
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Accessible.role: event.isSending ? Accessible.StaticText : Accessible.Button
Accessible.name: event.isSending ? l10n.get('event-sending') : l10n.get('event-resend')
ToolTip.text: event.isSending ? l10n.get('event-sending') : l10n.get('event-send-failed')
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hoverHandler.hovered
HoverHandler { id: hoverHandler }
TapHandler {
objectName: 'resendEventButton'
onTapped: {
room.resendMessage(event.txnId);
}
enabled: event.isFailed
}
}
Label {
objectName: 'timeIndicator'
visible: !compactMode && !event.isLocalEcho
text: event.formattedTime
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
}
Label {
objectName: 'editedIndicator'
visible: !compactMode && event.isEdited
text: l10n.get('event-edited-indicator')
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
}
Kazv.EventReadIndicator {
objectName: 'eventReadIndicator'
shouldShow: !compactMode && !event.isLocalEcho
model: event.readers()
}
ToolButton {
id: moreButton
objectName: 'moreButton'
visible: !compactMode
action: popupAction
display: AbstractButton.IconOnly
}
}
}
}
diff --git a/src/contents/ui/ReactToEventPopup.qml b/src/contents/ui/ReactToEventPopup.qml
index ad70036..a3e7a22 100644
--- a/src/contents/ui/ReactToEventPopup.qml
+++ b/src/contents/ui/ReactToEventPopup.qml
@@ -1,59 +1,59 @@
/*
* 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.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.13 as Kirigami
import '.' as Kazv
-Kirigami.OverlaySheet {
+Kazv.SelfDestroyableOverlaySheet {
id: popup
signal accepted(string text)
title: l10n.get('event-react-popup-title')
property var acceptAction: Kirigami.Action {
text: l10n.get('event-react-accept-action')
onTriggered: {
accepted(reactionTextInput.text);
popup.close();
}
}
property var cancelAction: Kirigami.Action {
text: l10n.get('event-react-cancel-action')
onTriggered: {
popup.close();
}
}
ColumnLayout {
Label {
Layout.fillWidth: true
text: l10n.get('event-react-with-prompt')
}
TextField {
id: reactionTextInput
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Button {
action: acceptAction
}
Button {
action: cancelAction
}
}
}
}
diff --git a/src/contents/ui/SelfDestroyableOverlaySheet.qml b/src/contents/ui/SelfDestroyableOverlaySheet.qml
new file mode 100644
index 0000000..cfd4ed3
--- /dev/null
+++ b/src/contents/ui/SelfDestroyableOverlaySheet.qml
@@ -0,0 +1,21 @@
+/*
+ * 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 org.kde.kirigami 2.13 as Kirigami
+
+Kirigami.OverlaySheet {
+ property var shouldSelfDestroy: false
+
+ onSheetOpenChanged: {
+ if (!sheetOpen && shouldSelfDestroy) {
+ destroy();
+ }
+ }
+}
diff --git a/src/contents/ui/event-types/Image.qml b/src/contents/ui/event-types/Image.qml
index ca3c63d..dc9a30b 100644
--- a/src/contents/ui/event-types/Image.qml
+++ b/src/contents/ui/event-types/Image.qml
@@ -1,105 +1,108 @@
/*
* 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.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import moe.kazv.mxc.kazv 0.0 as MK
import org.kde.kirigami 2.13 as Kirigami
import '.' as Types
import '..' as Kazv
Simple {
id: upper
property var gender: 'neutral'
property var body: event.content.body
property var curEvent: event
property var thumbnailFile: imageInfo.thumbnail_file || {}
property var thumbnailInfo: imageInfo.thumbnail_info || {}
property var thumbnailWidth: thumbnailInfo.w
property var thumbnailHeight: thumbnailInfo.h
property var imageInfo: event.content.info || {}
property var imageFile: event.content.file || {}
property var imageWidth: imageInfo.w
property var imageHeight: imageInfo.h
property var imageMxcUri: fileHandler.encryptedFileMxcUri || fileHandler.unencryptedFileMxcUri
property var innerContentWidth: upper.contentMaxWidth - bubble.bubbleSpacing
property var jobManager: kazvIOManager
property var mtxSdk: matrixSdk
property var isSticker: event.type === 'm.sticker'
Kazv.FileHandler {
id: fileHandler
eventContent: event.content
autoCache: true
kazvIOManager: upper.jobManager
matrixSdk: upper.mtxSdk
}
Types.MediaBubble {
id: bubble
eventContent: event.content
property var label: Kazv.SelectableText {
Layout.fillWidth: true
wrapMode: Text.Wrap
text: isSticker ? '' : l10n.get('event-message-image-sent', { gender, body })
}
property var image: Image {
id: image
Layout.fillWidth: true
Layout.preferredWidth: calcImageWidth()
Layout.preferredHeight: width < calcImageWidth() ? calcImageHeight() / calcImageWidth() * width : calcImageHeight()
source: fileHandler.kazvIOJob ? "" : fileHandler.cachedFile
horizontalAlignment: Image.AlignLeft
fillMode: Image.PreserveAspectFit
}
property var canAddSticker: !!event.content.url
- property var addStickerPopup: Kazv.AddStickerPopup {
- stickerPackList: matrixSdk.stickerPackList()
- event: upper.curEvent
+ property var addStickerPopupComp: Component {
+ Kazv.AddStickerPopup {
+ shouldSelfDestroy: true
+ stickerPackList: matrixSdk.stickerPackList()
+ event: upper.curEvent
+ }
}
property var addStickerAction: Kirigami.Action {
text: l10n.get('media-file-menu-add-sticker-action')
- onTriggered: bubble.addStickerPopup.open()
+ onTriggered: bubble.addStickerPopupComp.createObject(applicationWindow().overlay).open()
}
additionalMenuContent: canAddSticker ? [addStickerAction] : []
ColumnLayout {
id: layout
Layout.fillWidth: true
data: [
bubble.mediaFileMenu,
bubble.label,
bubble.image,
bubble.progressBar
]
}
}
function calcImageWidth() {
const eventSpecWidth = thumbnailWidth || imageWidth
return eventSpecWidth || image.implicitWidth || 1
}
function calcImageHeight() {
const eventSpecHeight = thumbnailHeight || imageHeight
return eventSpecHeight || image.implicitHeight || 1
}
}
diff --git a/src/resources.qrc b/src/resources.qrc
index 5a0a2e1..e97c706 100644
--- a/src/resources.qrc
+++ b/src/resources.qrc
@@ -1,86 +1,87 @@
<RCC>
<qresource prefix="/">
<file alias="main.qml">contents/ui/main.qml</file>
<file alias="PageManager.qml">contents/ui/PageManager.qml</file>
<file alias="LoginPage.qml">contents/ui/LoginPage.qml</file>
<file alias="MainPage.qml">contents/ui/MainPage.qml</file>
<file alias="TabView.qml">contents/ui/TabView.qml</file>
<file alias="Tab.qml">contents/ui/Tab.qml</file>
<file alias="ClosableScrollablePage.qml">contents/ui/ClosableScrollablePage.qml</file>
+ <file alias="SelfDestroyableOverlaySheet.qml">contents/ui/SelfDestroyableOverlaySheet.qml</file>
<file alias="RoomListView.qml">contents/ui/RoomListView.qml</file>
<file alias="RoomListViewItemDelegate.qml">contents/ui/RoomListViewItemDelegate.qml</file>
<file alias="RoomPage.qml">contents/ui/RoomPage.qml</file>
<file alias="RoomTimelineView.qml">contents/ui/RoomTimelineView.qml</file>
<file alias="SendMessageBox.qml">contents/ui/SendMessageBox.qml</file>
<file alias="EventView.qml">contents/ui/EventView.qml</file>
<file alias="EventViewWrapper.qml">contents/ui/EventViewWrapper.qml</file>
<file alias="EventViewCompact.qml">contents/ui/EventViewCompact.qml</file>
<file alias="EventSourceView.qml">contents/ui/EventSourceView.qml</file>
<file alias="Bubble.qml">contents/ui/Bubble.qml</file>
<file alias="MediaFileMenu.qml">contents/ui/MediaFileMenu.qml</file>
<file alias="KazvIOMenu.qml">contents/ui/KazvIOMenu.qml</file>
<file alias="ConfirmUploadPopup.qml">contents/ui/ConfirmUploadPopup.qml</file>
<file alias="StickerPicker.qml">contents/ui/StickerPicker.qml</file>
<file alias="AddStickerPopup.qml">contents/ui/AddStickerPopup.qml</file>
<file alias="ConfirmationOverlay.qml">contents/ui/ConfirmationOverlay.qml</file>
<file alias="event-types/Simple.qml">contents/ui/event-types/Simple.qml</file>
<file alias="event-types/Text.qml">contents/ui/event-types/Text.qml</file>
<file alias="event-types/Emote.qml">contents/ui/event-types/Emote.qml</file>
<file alias="event-types/Notice.qml">contents/ui/event-types/Notice.qml</file>
<file alias="event-types/State.qml">contents/ui/event-types/State.qml</file>
<file alias="event-types/TextTemplate.qml">contents/ui/event-types/TextTemplate.qml</file>
<file alias="event-types/Image.qml">contents/ui/event-types/Image.qml</file>
<file alias="event-types/File.qml">contents/ui/event-types/File.qml</file>
<file alias="event-types/Video.qml">contents/ui/event-types/Video.qml</file>
<file alias="event-types/Audio.qml">contents/ui/event-types/Audio.qml</file>
<file alias="event-types/MediaBubble.qml">contents/ui/event-types/MediaBubble.qml</file>
<file alias="event-types/Redacted.qml">contents/ui/event-types/Redacted.qml</file>
<file alias="event-types/Reaction.qml">contents/ui/event-types/Reaction.qml</file>
<file alias="event-types/Fallback.qml">contents/ui/event-types/Fallback.qml</file>
<file alias="TypingIndicator.qml">contents/ui/TypingIndicator.qml</file>
<file alias="EventReadIndicator.qml">contents/ui/EventReadIndicator.qml</file>
<file alias="SelectableText.qml">contents/ui/SelectableText.qml</file>
<file alias="ReactToEventPopup.qml">contents/ui/ReactToEventPopup.qml</file>
<file alias="FileHandler.qml">contents/ui/FileHandler.qml</file>
<file alias="ActionSettingsPage.qml">contents/ui/ActionSettingsPage.qml</file>
<file alias="CreateRoomPage.qml">contents/ui/CreateRoomPage.qml</file>
<file alias="JoinRoomPage.qml">contents/ui/JoinRoomPage.qml</file>
<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>
<file alias="SettingsPage.qml">contents/ui/SettingsPage.qml</file>
<file alias="settings/ProfileSettings.qml">contents/ui/settings/ProfileSettings.qml</file>
<file alias="settings/CacheSettings.qml">contents/ui/settings/CacheSettings.qml</file>
<file alias="room-settings/RoomSettingsPage.qml">contents/ui/room-settings/RoomSettingsPage.qml</file>
<file alias="room-settings/RoomTagHandler.qml">contents/ui/room-settings/RoomTagHandler.qml</file>
<file alias="room-settings/RoomMemberListPage.qml">contents/ui/room-settings/RoomMemberListPage.qml</file>
<file alias="room-settings/RoomInvitePage.qml">contents/ui/room-settings/RoomInvitePage.qml</file>
<file alias="device-mgmt/Device.qml">contents/ui/device-mgmt/Device.qml</file>
<file alias="device-mgmt/DeviceList.qml">contents/ui/device-mgmt/DeviceList.qml</file>
<file alias="UserPage.qml">contents/ui/UserPage.qml</file>
<file alias="shortcuts/ActionCollection.qml">contents/ui/shortcuts/ActionCollection.qml</file>
<file alias="shortcuts/ActionItem.qml">contents/ui/shortcuts/ActionItem.qml</file>
<file alias="shortcuts/ActionSettings.qml">contents/ui/shortcuts/ActionSettings.qml</file>
<file alias="shortcuts/ShortcutInput.qml">contents/ui/shortcuts/ShortcutInput.qml</file>
<file alias="MessageNotification.qml">contents/ui/MessageNotification.qml</file>
<file alias="Notifier.qml">contents/ui/Notifier.qml</file>
<file alias="l10n.js">js/l10n.js</file>
<file alias="fluent-bundle.js">js/transformed-libs/fluent-bundle.js</file>
<file alias="fluent-sequence.js">js/transformed-libs/fluent-sequence.js</file>
<file alias="fluent-langneg.js">js/transformed-libs/fluent-langneg.js</file>
<file alias="bundled-deps.js">js/transformed-libs/bundled-deps.js</file>
<file alias="global-this.js">js/global-this.js</file>
<file alias="matrix-helpers.js">js/matrix-helpers.js</file>
</qresource>
</RCC>
diff --git a/src/tests/quick-tests/tst_EventView.qml b/src/tests/quick-tests/tst_EventView.qml
index 43bc517..a7c7eed 100644
--- a/src/tests/quick-tests/tst_EventView.qml
+++ b/src/tests/quick-tests/tst_EventView.qml
@@ -1,513 +1,529 @@
/*
* 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 moe.kazv.mxc.kazv 0.0 as MK
import '../../contents/ui' as Kazv
import 'test-helpers.js' as Helpers
import 'test-helpers' as TestHelpers
Item {
id: item
width: 800
height: 600
property var promiseComp: Component {
TestHelpers.MatrixPromiseMock {
}
}
property var activateUserPageCalled: 0
function activateUserPage() {
++activateUserPageCalled;
}
property var timeline: ({
gaps: [],
})
property var room: ({
test_resendMessageCalled: 0,
test_resendMessageLastTxnId: '',
test_removeLocalEchoCalled: 0,
test_removeLocalEchoLastTxnId: '',
test_ensureStateEventCalled: 0,
test_ensureStateEventCalledArgs: [],
test_ensureStateEventPromise: null,
resendMessage: (txnId) => {
++item.room.test_resendMessageCalled;
item.room.test_resendMessageLastTxnId = txnId;
},
removeLocalEcho: (txnId) => {
++item.room.test_removeLocalEchoCalled;
item.room.test_removeLocalEchoLastTxnId = txnId;
},
ensureStateEvent: (type, stateKey) => {
++item.room.test_ensureStateEventCalled;
item.room.test_ensureStateEventCalledArgs = [type, stateKey];
item.room.test_ensureStateEventPromise = promiseComp.createObject(item);
return item.room.test_ensureStateEventPromise;
},
messageById: (_id) => item.textEvent,
member: (_id) => ({}),
})
property var l10n: Helpers.fluentMock
property var matrixSdk: TestHelpers.MatrixSdkMock {
property var userId: '@foo:tusooa.xyz'
}
property var sdkVars: QtObject {
property var userGivenNicknameMap: QtObject {
property var map: ({
'@foo:tusooa.xyz': 'something',
})
}
}
property var kazvIOManager: TestHelpers.KazvIOManagerMock {}
property var localEcho: ({
eventId: '',
sender: '',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.text',
body: 'some body',
},
encrypted: false,
isState: false,
unsignedData: {},
isLocalEcho: true,
isSending: true,
isFailed: false,
txnId: 'some-txn-id',
})
property var failedLocalEcho: Object.assign({}, item.localEcho, { isSending: false, isFailed: true, })
property var textEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.text',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var htmlEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.text',
body: '**some body**',
format: 'org.matrix.custom.html',
formatted_body: '<strong>some body</strong>',
},
formattedTime: '4:06 P.M.',
})
property var replyHtmlEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
replyingToEventId: '$xxx',
content: {
msgtype: 'm.text',
body: '> some quote\n**some body**',
format: 'org.matrix.custom.html',
formatted_body: '<mx-reply><blockquote>some quote</blockquote></mx-reply><strong>some body</strong>',
},
formattedTime: '4:06 P.M.',
})
property var replyNoQuoteHtmlEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
replyingToEventId: '$xxx',
content: {
msgtype: 'm.text',
body: '**some body**',
format: 'org.matrix.custom.html',
formatted_body: '<strong>some body</strong>',
},
formattedTime: '4:06 P.M.',
})
property var redactedEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {},
redacted: true,
formattedTime: '4:06 P.M.',
})
property var imageEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.image',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var eventBySomeoneElse: ({
eventId: '',
sender: '@bar:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.image',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var eventTextBySomeoneElse: ({
eventId: '',
sender: '@bar:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'm.text',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var ignoredEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
relationType: 'm.replace',
stateKey: '',
content: {
msgtype: 'm.image',
body: 'some body',
},
formattedTime: '4:06 P.M.',
})
property var unknownTypeEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'moe.kazv.mxc.unknown.event',
stateKey: '',
content: {
msgtype: 'm.image',
body: 'some body',
},
})
property var unknownMsgtypeEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'moe.kazv.mxc.unknown.msgtype',
body: 'some body',
},
})
property var cannotDecryptEvent: ({
eventId: '',
sender: '@foo:tusooa.xyz',
type: 'm.room.message',
stateKey: '',
content: {
msgtype: 'moe.kazv.mxc.cannot.decrypt',
body: 'some body',
},
})
property var sender: ({
membership: 'join',
userId: '@foo:tusooa.xyz',
name: 'foo',
avatarMxcUri: '',
})
property var senderOther: ({
membership: 'join',
userId: '@bar:tusooa.xyz',
name: 'bar',
avatarMxcUri: '',
})
ColumnLayout {
anchors.fill: parent
Kazv.EventView {
Layout.fillWidth: true
id: eventView
event: item.localEcho
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewFailed
event: item.failedLocalEcho
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewText
event: item.textEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewHtml
event: item.htmlEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewHtmlReply
event: item.replyHtmlEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewHtmlReplyNoQuote
event: item.replyNoQuoteHtmlEvent
sender: item.sender
}
Kazv.EventView {
Layout.fillWidth: true
id: eventViewRedacted
event: item.redactedEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewUnknownType
event: item.unknownTypeEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewUnknownMsgtype
event: item.unknownMsgtypeEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewCannotDecrypt
event: item.cannotDecryptEvent
sender: item.sender
}
Kazv.EventView {
id: eventViewCollapseSameSender
event: item.imageEvent
sender: item.sender
prevEvent: item.redactedEvent
}
Kazv.EventView {
id: eventViewCollapseLocalEcho
event: item.localEcho
sender: item.sender
prevEvent: item.redactedEvent
}
Kazv.EventView {
id: eventViewDontCollapseDifferentSender
event: item.imageEvent
sender: item.sender
prevEvent: item.eventBySomeoneElse
}
Kazv.EventView {
id: eventViewDontCollapseLocalEchoDifferentSender
event: item.localEcho
sender: item.sender
prevEvent: item.eventBySomeoneElse
}
Kazv.EventView {
id: eventViewDontCollapseIgnoredEvent
event: item.imageEvent
sender: item.sender
prevEvent: item.ignoredEvent
}
Kazv.EventView {
id: eventViewTextBySomeoneElse
event: item.eventTextBySomeoneElse
sender: item.senderOther
}
}
TestCase {
id: eventViewTest
name: 'EventViewTest'
when: windowShown
function init() {
eventView.event = item.localEcho;
item.room.test_resendMessageCalled = 0;
item.room.test_resendMessageLastTxnId = '';
item.room.test_removeLocalEchoCalled = 0;
item.room.test_removeLocalEchoLastTxnId = '';
item.room.test_ensureStateEventCalled = 0;
item.room.test_ensureStateEventCalledArgs = [];
item.room.test_ensureStateEventPromise = null;
item.activateUserPageCalled = 0;
}
function test_localEcho() {
const indicator = findChild(eventView, 'localEchoIndicator');
verify(indicator);
verify(indicator.source === 'state-sync');
verify(indicator.Accessible.name === l10n.get('event-sending'));
const resendButton = findChild(eventView, 'resendEventButton');
verify(resendButton);
verify(!resendButton.enabled);
verify(!findChild(eventView, 'timeIndicator').visible);
}
function test_localEchoFailedRemove() {
const indicator = findChild(eventViewFailed, 'localEchoIndicator');
verify(indicator);
verify(indicator.visible);
verify(indicator.source === 'state-warning');
verify(indicator.Accessible.name === l10n.get('event-resend'));
- const deleteAction = findChild(eventViewFailed, 'deleteMenuItem');
+ const menuComp = findChild(eventViewFailed, 'bubble').menuComp;
+ const menu = menuComp.createObject(menuComp.parent);
+ const deleteAction = findChild(menu, 'deleteMenuItem');
deleteAction.trigger();
tryVerify(() => item.room.test_removeLocalEchoCalled === 1);
tryVerify(() => item.room.test_removeLocalEchoLastTxnId === 'some-txn-id');
verify(item.room.test_resendMessageCalled === 0);
verify(item.room.test_resendMessageLastTxnId === '');
}
function test_sentMessage() {
const indicator = findChild(eventViewText, 'localEchoIndicator');
verify(indicator);
verify(!indicator.visible);
verify(findChild(eventViewText, 'timeIndicator').visible);
}
function test_htmlMessage() {
const text = findChild(eventViewHtml, 'textEventContent');
verify(text.textFormat === TextEdit.RichText);
verify(text.text.includes('some body'));
}
function test_htmlMessageReply() {
const text = findChild(eventViewHtmlReply, 'textEventContent');
verify(text.textFormat === TextEdit.RichText);
verify(!text.text.includes('<blockquote>'));
}
function test_htmlMessageReplyNoQuote() {
const text = findChild(eventViewHtmlReplyNoQuote, 'textEventContent');
verify(text.textFormat === TextEdit.RichText);
verify(!text.text.includes('<blockquote>'));
verify(text.text.includes('some body'));
}
function test_menuText() {
- const menu = findChild(eventViewText, 'bubbleContextMenu');
+ const menuComp = findChild(eventViewText, 'bubble').menuComp;
+ const menu = menuComp.createObject(menuComp.parent);
verify(menu);
tryVerify(() => findChild(menu, 'replyToMenuItem').enabled);
tryVerify(() => findChild(menu, 'editMenuItem').enabled);
tryVerify(() => findChild(menu, 'deleteMenuItem').enabled);
}
function test_menuRedacted() {
- const menu = findChild(eventViewRedacted, 'bubbleContextMenu');
+ const menuComp = findChild(eventViewRedacted, 'bubble').menuComp;
+ const menu = menuComp.createObject(menuComp.parent);
verify(menu);
tryVerify(() => !findChild(menu, 'replyToMenuItem').enabled);
tryVerify(() => !findChild(menu, 'deleteMenuItem').enabled);
}
function test_fallback() {
verify(findChild(eventViewUnknownType, 'fallbackIcon'));
verify(findChild(eventViewUnknownType, 'textEventContent').text ===
l10n.get('event-fallback-text', { type: unknownTypeEvent.type }));
verify(findChild(eventViewUnknownMsgtype, 'fallbackIcon'));
verify(findChild(eventViewUnknownMsgtype, 'textEventContent').text ===
l10n.get('event-msgtype-fallback-text', { msgtype: unknownMsgtypeEvent.content.msgtype }));
verify(findChild(eventViewCannotDecrypt, 'fallbackIcon'));
verify(findChild(eventViewCannotDecrypt, 'textEventContent').text ===
l10n.get('event-cannot-decrypt-text'));
}
function test_collapseSender() {
verify(!findChild(eventViewCollapseSameSender, 'senderAvatar').visible);
verify(findChild(eventViewCollapseSameSender, 'senderCollapsedPlaceholder').visible);
verify(!findChild(eventViewCollapseLocalEcho, 'senderAvatar').visible);
verify(findChild(eventViewCollapseLocalEcho, 'senderCollapsedPlaceholder').visible);
verify(findChild(eventViewDontCollapseDifferentSender, 'senderAvatar').visible);
verify(!findChild(eventViewDontCollapseDifferentSender, 'senderCollapsedPlaceholder').visible);
verify(findChild(eventViewDontCollapseLocalEchoDifferentSender, 'senderAvatar').visible);
verify(!findChild(eventViewDontCollapseLocalEchoDifferentSender, 'senderCollapsedPlaceholder').visible);
verify(findChild(eventViewDontCollapseIgnoredEvent, 'senderAvatar').visible);
verify(!findChild(eventViewDontCollapseIgnoredEvent, 'senderCollapsedPlaceholder').visible);
}
function test_eventByOther() {
- const menu = findChild(eventViewTextBySomeoneElse, 'bubbleContextMenu');
+ const menuComp = findChild(eventViewTextBySomeoneElse, 'bubble').menuComp;
+ const menu = menuComp.createObject(menuComp.parent);
verify(menu);
tryVerify(() => !findChild(menu, 'editMenuItem').enabled);
}
+ function test_menuSelfDestruct() {
+ const menuComp = findChild(eventViewTextBySomeoneElse, 'bubble').menuComp;
+ const menu = menuComp.createObject(menuComp.parent);
+ verify(menu);
+ menu.popup(item);
+ menu.dismiss();
+ const menu2 = menuComp.createObject(menuComp.parent);
+ menu2.popup(item);
+ menu2.dismiss();
+ }
+
function test_userLink() {
const text = findChild(eventViewText, 'textEventContent');
text.linkActivated('https://matrix.to/#/@mew:example.com');
tryVerify(() => item.room.test_ensureStateEventCalled);
verify(Helpers.deepEqual(
item.room.test_ensureStateEventCalledArgs,
['m.room.member', '@mew:example.com']));
item.room.test_ensureStateEventPromise.resolve(true, {});
tryVerify(() => item.activateUserPageCalled);
}
function test_senderNameOverrided() {
const name = findChild(eventViewText, 'userNicknameText');
compare(name.text, l10n.get(
'user-name-overrided',
{ overridedName: 'something', globalName: 'foo' }));
}
function test_senderNameNotOverrided() {
const name = findChild(eventViewTextBySomeoneElse, 'userNicknameText');
compare(name.text, 'bar');
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Oct 29, 2:34 AM (4 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1797427
Default Alt Text
(39 KB)

Event Timeline