From 0afba7dc613c97941b18d12c4ef3d751cce7f2ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Mon, 24 Nov 2025 13:58:42 +0100 Subject: [PATCH 1/3] fix: improve emoji actions and other chat popups - StatusMessageEmojiReactions: update according to the latest Figma designs, simplify the `showReactionAuthors()` function - StatusMessage: disable the hover quick actions on mobile; the context menu works much better and has the same actions (including the emoji quick reactions) - StatusTextMessage: make it `enabled: false` in order to support the long-press context menu on mobile - MessageView: streamline context menus opening that works with both mouse and touch - WalletAccountHeader: support fallback account icons (non-emojis) - RenameAccountModal: fix changing the emoji of an existing wallet account - removed some unused properties - update and fixed the relevant SB pages - do no use hardcoded margins/paddings - fix some QML warnings Fixes #19328 Fixes #19327 Iterates: #19199 --- storybook/pages/StatusEmojiPopupPage.qml | 19 +++- storybook/pages/StatusMessagePage.qml | 15 ++-- storybook/src/Models/ReactionsModels.qml | 61 ++++++------- .../src/StatusQ/Components/StatusMessage.qml | 27 +++--- .../StatusMessageEmojiReactions.qml | 89 +++++++------------ .../statusMessage/StatusTextMessage.qml | 7 +- ui/StatusQ/src/StatusQ/Core/Utils/Emoji.qml | 4 - .../AppLayouts/Chat/panels/UserListPanel.qml | 3 +- .../Profile/popups/RenameAccountModal.qml | 4 +- .../AppLayouts/Profile/views/SyncingView.qml | 2 +- .../Wallet/panels/WalletAccountHeader.qml | 5 ++ .../AppLayouts/Wallet/views/RightTabView.qml | 2 +- .../shared/controls/chat/EmojiReaction.qml | 4 +- .../shared/popups/addaccount/states/Main.qml | 2 +- ui/imports/shared/views/chat/MessageView.qml | 39 ++++---- 15 files changed, 132 insertions(+), 151 deletions(-) diff --git a/storybook/pages/StatusEmojiPopupPage.qml b/storybook/pages/StatusEmojiPopupPage.qml index 8ded6b13c98..23f61d1f2f1 100644 --- a/storybook/pages/StatusEmojiPopupPage.qml +++ b/storybook/pages/StatusEmojiPopupPage.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import StatusQ import StatusQ.Core.Theme import StatusQ.Core.Utils as StatusQUtils @@ -21,6 +22,7 @@ SplitView { QtObject { id: d property string lastSelectedEmoji: "N/A" + property string lastSelectedEmojiHexcode: "" } Pane { @@ -62,6 +64,7 @@ SplitView { onEmojiSelected: function(emoji, atCu, hexcode) { logs.logEvent("onEmojiSelected", ["emoji", "atCu", "hexcode"], arguments) d.lastSelectedEmoji = emoji + d.lastSelectedEmojiHexcode = hexcode } } } @@ -79,14 +82,26 @@ SplitView { text: "Clear settings (reload to take effect)" onClicked: { d.lastSelectedEmoji = "" + d.lastSelectedEmojiHexcode = "" settings.recentEmojis = [] settings.skinColor = "" settings.sync() } } - Label { - text: "Last selected: %1 ('%2')".arg(d.lastSelectedEmoji).arg(settings.recentEmojis[0] ?? "") + RowLayout { + Label { + text: "Last selected: %1 ('%2')".arg(d.lastSelectedEmoji).arg(d.lastSelectedEmojiHexcode) + } + ToolButton { + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + text: "📋" + enabled: !!d.lastSelectedEmojiHexcode + onClicked: ClipboardUtils.setText(d.lastSelectedEmojiHexcode) + ToolTip.text: "Copy to clipboard" + ToolTip.visible: hovered + } } Button { diff --git a/storybook/pages/StatusMessagePage.qml b/storybook/pages/StatusMessagePage.qml index 9f0466b5b83..e2e7651f2f7 100644 --- a/storybook/pages/StatusMessagePage.qml +++ b/storybook/pages/StatusMessagePage.qml @@ -23,11 +23,11 @@ SplitView { readonly property var reactionsModels: ReactionsModels {} readonly property var messageWithThreeReactions: [{ - timestamp: 1667937830123, + timestamp: new Date().valueOf(), senderId: "zq123456790", senderDisplayName: "Alice", contentType: StatusMessage.ContentType.Text, - message: "This message has 3 reactions", + message: "This message has 3 reactions and should have current timestamp", isContact: true, isAReply: false, trustIndicator: StatusContactVerificationIcons.TrustedType.None, @@ -39,7 +39,7 @@ SplitView { senderId: "zq123456790", senderDisplayName: "Alice", contentType: StatusMessage.ContentType.Text, - message: "This message has 20 reactions", + message: "This message has 20 reactions (max)", isContact: true, isAReply: false, trustIndicator: StatusContactVerificationIcons.TrustedType.None, @@ -265,19 +265,20 @@ SplitView { sender.isEnsVerified: isEnsVerified sender.profileImage { name: model.profileImage || "" - colorId: index + colorId: index % Theme.palette.userCustomizationColors.length } album: model.contentType === StatusMessage.ContentType.Image ? d.exampleAlbum : [] albumCount: model.contentType === StatusMessage.ContentType.Image ? d.exampleAlbum.length : 0 } replyDetails { - amISender: true + amISender: index % 2 sender.id: "0xdeadbeef" + sender.displayName: "Foobar" sender.profileImage { width: 20 height: 20 - name: ModelsData.icons.dribble + name: index % 2 ? ModelsData.icons.dribble : ModelsData.icons.socks } messageText: ModelsData.descriptions.mediumLoremIpsum } @@ -289,6 +290,8 @@ SplitView { onResendClicked: logs.logEvent("StatusMessage::resendClicked") onLinkActivated: logs.logEvent("StatusMessage::linkActivated", ["link"], arguments) onImageClicked: logs.logEvent("StatusMessage::imageClicked") + onAddReactionClicked: logs.logEvent("StatusMessage::addReactionClicked") + onToggleReactionClicked: logs.logEvent("StatusMessage::toggleReactionClicked", ["hexcode"], arguments) } } } diff --git a/storybook/src/Models/ReactionsModels.qml b/storybook/src/Models/ReactionsModels.qml index 9af313a83bb..56009ae70d1 100644 --- a/storybook/src/Models/ReactionsModels.qml +++ b/storybook/src/Models/ReactionsModels.qml @@ -1,150 +1,147 @@ -import QtQuick - +import QtQml QtObject { readonly property var threeReactions: [ { - emoji: "😄", + emoji: "1f600", didIReactWithThisEmoji: true, numberOfReactions: 1, jsonArrayOfUsersReactedWithThisEmoji: "[\"You\"]" }, { - emoji: "🕵️‍♀", + emoji: "1f575-fe0f-200d-2642-fe0f", didIReactWithThisEmoji: false, numberOfReactions: 2, jsonArrayOfUsersReactedWithThisEmoji: "[\"Bob\", \"John\"]" }, { - emoji: "😂", + emoji: "1f602", didIReactWithThisEmoji: true, numberOfReactions: 5, jsonArrayOfUsersReactedWithThisEmoji: "[\"You\", \"Sally\", \"Tom\", \"Eve\", \"Raj\"]" } - ] + readonly property var twentyReactions: [ { - emoji: "😄", + emoji: "1f604", didIReactWithThisEmoji: true, numberOfReactions: 1, jsonArrayOfUsersReactedWithThisEmoji: "[\"You\"]" }, { - emoji: "🕵️‍♀", + emoji: "1fae3", didIReactWithThisEmoji: false, - numberOfReactions: 232, + numberOfReactions: 23, jsonArrayOfUsersReactedWithThisEmoji: "[\"Bob\", \"John\"]" }, { - emoji: "😂", + emoji: "1f602", didIReactWithThisEmoji: true, numberOfReactions: 5, jsonArrayOfUsersReactedWithThisEmoji: "[\"You\", \"Sally\", \"Tom\", \"Eve\", \"Raj\"]" }, { - emoji: "❤️", + emoji: "2764", didIReactWithThisEmoji: false, numberOfReactions: 8, jsonArrayOfUsersReactedWithThisEmoji: "[\"Mia\", \"Noah\", \"Liam\", \"Olivia\", \"Ava\", \"Emma\", \"Lucas\", \"Zoe\"]" }, { - emoji: "👍", + emoji: "1f44d", didIReactWithThisEmoji: true, numberOfReactions: 3, jsonArrayOfUsersReactedWithThisEmoji: "[\"You\", \"Ben\", \"Claire\"]" }, { - emoji: "🎉", + emoji: "1f389", didIReactWithThisEmoji: false, - numberOfReactions: 4, - jsonArrayOfUsersReactedWithThisEmoji: "[\"Ivy\", \"Ken\", \"Lara\", \"Omar\"]" + numberOfReactions: 16, + jsonArrayOfUsersReactedWithThisEmoji: "[\"Mia\", \"Noah\", \"Liam\", \"Olivia\", \"Ava\", \"Emma\", \"Lucas\", \"Zoe\", \"Mia\", \"Noah\", \"Liam\", \"Olivia\", \"Ava\", \"Emma\", \"Lucas\", \"Zoe\"]" }, { - emoji: "😮", + emoji: "1f62e", didIReactWithThisEmoji: false, numberOfReactions: 1, jsonArrayOfUsersReactedWithThisEmoji: "[\"Zed\"]" }, { - emoji: "😢", + emoji: "1f972", didIReactWithThisEmoji: false, numberOfReactions: 2, jsonArrayOfUsersReactedWithThisEmoji: "[\"Martha\", \"Gus\"]" }, { - emoji: "🔥", + emoji: "1f525", didIReactWithThisEmoji: true, numberOfReactions: 6, jsonArrayOfUsersReactedWithThisEmoji: "[\"You\", \"Alex\", \"Sam\", \"Nina\", \"Pax\", \"Rae\"]" }, { - emoji: "🙏", + emoji: "1f64f", didIReactWithThisEmoji: false, numberOfReactions: 2, jsonArrayOfUsersReactedWithThisEmoji: "[\"Hana\", \"Ike\"]" }, { - emoji: "😅", + emoji: "1f917", didIReactWithThisEmoji: true, numberOfReactions: 2, jsonArrayOfUsersReactedWithThisEmoji: "[\"You\", \"Pete\"]" }, { - emoji: "😎", + emoji: "1f636-200d-1f32b-fe0f", didIReactWithThisEmoji: false, numberOfReactions: 3, jsonArrayOfUsersReactedWithThisEmoji: "[\"Ruth\", \"Vik\", \"Jill\"]" }, { - emoji: "🤔", + emoji: "1f914", didIReactWithThisEmoji: false, numberOfReactions: 1, jsonArrayOfUsersReactedWithThisEmoji: "[\"Oli\"]" }, { - emoji: "🤯", + emoji: "1f92f", didIReactWithThisEmoji: true, numberOfReactions: 7, jsonArrayOfUsersReactedWithThisEmoji: "[\"You\", \"Abe\", \"Maya\", \"Noel\", \"Cory\", \"Lina\", \"Zara\"]" }, { - emoji: "🎶", + emoji: "1f3b6", didIReactWithThisEmoji: false, numberOfReactions: 2, jsonArrayOfUsersReactedWithThisEmoji: "[\"Tess\", \"Bea\"]" }, { - emoji: "💯", + emoji: "1f4af", didIReactWithThisEmoji: true, numberOfReactions: 9, jsonArrayOfUsersReactedWithThisEmoji: "[\"You\", \"Gabe\", \"Rin\", \"Seth\", \"Moe\", \"Luz\", \"Ira\", \"Noa\", \"Pam\"]" }, { - emoji: "👀", + emoji: "1f440", didIReactWithThisEmoji: false, numberOfReactions: 1, jsonArrayOfUsersReactedWithThisEmoji: "[\"Kai\"]" }, { - emoji: "😜", + emoji: "1f609", didIReactWithThisEmoji: true, numberOfReactions: 3, jsonArrayOfUsersReactedWithThisEmoji: "[\"You\", \"Dot\", \"Max\"]" }, { - emoji: "🥳", + emoji: "1f973", didIReactWithThisEmoji: false, numberOfReactions: 4, jsonArrayOfUsersReactedWithThisEmoji: "[\"June\", \"Fay\", \"Roy\", \"Skye\"]" }, { - emoji: "🤝", + emoji: "1f37b", didIReactWithThisEmoji: false, - numberOfReactions: 2, + numberOfReactions: 123456789, jsonArrayOfUsersReactedWithThisEmoji: "[\"Ivy\", \"Omar\"]" } - ] - // Component.onCompleted: append(data) } diff --git a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml index e79e0f3c382..81d56245577 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml @@ -102,7 +102,6 @@ Control { signal linkActivated(string link) signal hoverChanged(string messageId, bool hovered) - signal activeChanged(string messageId, bool active) function startMessageFoundAnimation() { messageFoundAnimation.restart(); @@ -216,16 +215,16 @@ Control { objectName: "StatusMessage_replyDetails" replyDetails: root.replyDetails profileClickable: root.profileClickable - onReplyProfileClicked: root.replyProfileClicked(sender, mouse) - onMessageClicked: root.replyMessageClicked(mouse) + onReplyProfileClicked: (sender, mouse) => root.replyProfileClicked(sender, mouse) + onMessageClicked: mouse => root.replyMessageClicked(mouse) } } RowLayout { Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - spacing: 8 + Layout.leftMargin: Theme.padding + Layout.rightMargin: Theme.padding + spacing: Theme.halfPadding StatusUserImage { id: profileImage @@ -290,9 +289,7 @@ Control { highlightedLink: root.highlightedLink linkAddressAndEnsName: root.linkAddressAndEnsName disabledTooltipText: root.disabledTooltipText - onLinkActivated: { - root.linkActivated(link); - } + onLinkActivated: link => root.linkActivated(link) textField.onHoveredLinkChanged: { root.hoveredLink = hoveredLink; } @@ -305,7 +302,7 @@ Control { sourceComponent: Column { id: imagesColumn - spacing: 8 + spacing: Theme.halfPadding Loader { active: root.messageDetails.messageText !== "" anchors.left: parent.left @@ -318,9 +315,7 @@ Control { allowShowMore: !root.isInPinnedPopup textField.anchors.rightMargin: root.isInPinnedPopup ? Theme.xlPadding : 0 // margin for the "Unpin" floating button highlightedLink: root.highlightedLink - onLinkActivated: { - root.linkActivated(link); - } + onLinkActivated: link => root.linkActivated(link) } } @@ -349,7 +344,7 @@ Control { model: attachmentsModel delegate: StatusImageMessage { source: model.source - onClicked: root.imageClicked(image, mouse, imageSource) + onClicked: (image, mouse, imageSource) => root.imageClicked(image, mouse, imageSource) shapeType: StatusImageMessage.ShapeType.LEFT_ROUNDED } } @@ -404,7 +399,6 @@ Control { onHoverChanged: (hovered) => root.hoverChanged(messageId, hovered) - isCurrentUser: root.messageDetails.amISender onAddEmojiClicked: (sender, mouse) => root.addReactionClicked(sender, mouse) onToggleReaction: (hexcode) => root.toggleReactionClicked(hexcode) } @@ -413,8 +407,11 @@ Control { } } + // TODO remove me completely? literally the same as MessageContextMenuView, and overlaps the message text Loader { active: root.hovered && root.quickActions.length > 0 + && !Utils.isMobile // hover menu disabled on mobile; we use the MessageContextMenuView + visible: active anchors.right: parent.right anchors.rightMargin: Theme.padding anchors.top: parent.top diff --git a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageEmojiReactions.qml b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageEmojiReactions.qml index 1fb893dca7c..9bb4d06ac84 100644 --- a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageEmojiReactions.qml +++ b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageEmojiReactions.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts import StatusQ.Core import StatusQ.Core.Theme @@ -10,23 +11,18 @@ import StatusQ.Components Flow { id: root - spacing: Theme.padding / 2 + spacing: Theme.halfPadding / 2 signal addEmojiClicked(var sender, var mouse) signal hoverChanged(bool hovered) signal toggleReaction(string hexcode) - property bool isCurrentUser property var reactionsModel property bool limitReached: false QtObject { id: d - function lastTwoItems(nodes) { - return nodes.join(qsTr(" and ")); - } - function showReactionAuthors(jsonArrayOfUsersReactedWithThisEmoji, emoji) { if (!jsonArrayOfUsersReactedWithThisEmoji) { return @@ -37,48 +33,29 @@ Flow { return } - let author; - if (listOfUsers.length === 1) { - author = listOfUsers[0] - } else if (listOfUsers.length === 2) { - author = lastTwoItems(listOfUsers); - } else { - var leftNode = []; - var rightNode = []; - const maxReactions = 12 - let maximum = Math.min(maxReactions, listOfUsers.length) - - if (listOfUsers.length > maxReactions) { - leftNode = listOfUsers.slice(0, maxReactions); - rightNode = listOfUsers.slice(maxReactions, listOfUsers.length); - return (rightNode.length === 1) ? - lastTwoItems([leftNode.join(", "), rightNode[0]]) : - lastTwoItems([leftNode.join(", "), qsTr("%1 more").arg(rightNode.length)]); - } + const maxReactions = 12 + const extraCount = listOfUsers.splice(maxReactions).length + if (extraCount > 0) + listOfUsers.push(qsTr("%1 more").arg(extraCount)) // "a, b, ... and N more" - leftNode = listOfUsers.slice(0, maximum - 1); - rightNode = listOfUsers.slice(maximum - 1, listOfUsers.length); - author = lastTwoItems([leftNode.join(", "), rightNode[0]]) - } - return qsTr("%1 reacted with %2") - .arg(author) - .arg(StatusQUtils.Emoji.fromCodePoint(emoji)); + const author = Qt.locale(Qt.uiLanguage).createSeparatedList(listOfUsers) // "a, b, c and d" + return qsTr("%1 reacted with %2").arg(author).arg(StatusQUtils.Emoji.fromCodePoint(emoji)) } } Repeater { model: root.reactionsModel - Control { + Button { id: reactionDelegate - topPadding: Theme.padding / 2.5 - bottomPadding: Theme.padding / 2.5 - leftPadding: Theme.padding / 2 - rightPadding: Theme.padding / 2 + verticalPadding: Theme.halfPadding + leftPadding: Theme.halfPadding + rightPadding: Theme.halfPadding / 2 + spacing: Theme.halfPadding / 2 background: Rectangle { - radius: 8 + radius: Theme.radius color: { if (reactionDelegate.hovered) { return Theme.palette.statusMessage.emojiReactionBackgroundHovered @@ -89,22 +66,19 @@ Flow { border.color: reactionDelegate.hovered ? Theme.palette.statusMessage.emojiReactionBorderHovered : Theme.palette.primaryColor1 } - contentItem: Row { - spacing: Theme.padding / 2 + contentItem: RowLayout { + spacing: reactionDelegate.spacing - StatusEmoji { + StatusIcon { objectName: "emojiReaction" - id: statusEmoji - anchors.verticalCenter: parent.verticalCenter - width: Theme.fontSize17 - height: Theme.fontSize17 - emojiId: model.emoji + Layout.preferredWidth: 16 + Layout.preferredHeight: 16 + icon: Theme.emoji(model.emoji) } StatusBaseText { - anchors.verticalCenter: parent.verticalCenter text: model.numberOfReactions - font.pixelSize: Theme.fontSize14 + font.pixelSize: Theme.fontSize13 } } @@ -114,23 +88,20 @@ Flow { text: d.showReactionAuthors(model.jsonArrayOfUsersReactedWithThisEmoji, model.emoji) || "" } - StatusMouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onEntered: { - root.hoverChanged(true) - } - onExited: { - root.hoverChanged(false) - } - onClicked: root.toggleReaction(model.emoji) + HoverHandler { + cursorShape: hovered ? Qt.PointingHandCursor : undefined + onHoveredChanged: root.hoverChanged(hovered) } + + onClicked: root.toggleReaction(model.emoji) } } StatusFlatButton { + width: 36 + height: 32 + horizontalPadding: Theme.halfPadding + verticalPadding: Theme.halfPadding/2 visible: root.enabled icon.name: "reaction-b" size: StatusBaseButton.Size.Small diff --git a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusTextMessage.qml b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusTextMessage.qml index 555f8245fff..b3956b70fca 100644 --- a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusTextMessage.qml +++ b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusTextMessage.qml @@ -81,7 +81,7 @@ Item { Rectangle { width: 1 height: chatText.height - radius: 8 + radius: Theme.radius visible: d.isQuote color: Theme.palette.baseColor1 } @@ -96,7 +96,7 @@ Item { height: effectiveHeight + d.showMoreHeight / 2 anchors.left: parent.left - anchors.leftMargin: d.isQuote ? 8 : 0 + anchors.leftMargin: d.isQuote ? Theme.halfPadding : 0 anchors.right: parent.right opacity: !showMoreOpacityMask.active && !horizontalOpacityMask.active ? 1 : 0 text: d.text @@ -108,7 +108,8 @@ Item { textFormat: Text.RichText wrapMode: root.convertToSingleLine ? Text.NoWrap : Text.Wrap readOnly: true - selectByMouse: true + selectByMouse: true // applies to mouse only, not touch + enabled: !Utils.isMobile // eats the touch events, thus breaking the context menu since this is an edit (albeit readonly) onLinkActivated: function(link) { if(d.showDisabledTooltipForAddressEnsName(link)) { return diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/Emoji.qml b/ui/StatusQ/src/StatusQ/Core/Utils/Emoji.qml index cc5cc027980..d2c53b5d9d3 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/Emoji.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/Emoji.qml @@ -15,10 +15,6 @@ QtObject { "small": "18x18", "verySmall": "16x16" } - readonly property var format: { - "png": "png", - "svg": "svg" - } readonly property string base: Qt.resolvedUrl("../../../assets/twemoji/svg/") property var emojiJSON: EmojiJSON diff --git a/ui/app/AppLayouts/Chat/panels/UserListPanel.qml b/ui/app/AppLayouts/Chat/panels/UserListPanel.qml index 800c2a7f676..737c2a24b16 100644 --- a/ui/app/AppLayouts/Chat/panels/UserListPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/UserListPanel.qml @@ -152,7 +152,6 @@ Item { userIcon: model.icon, trustStatus: model.trustStatus, onlineStatus: model.onlineStatus, - ensVerified: model.isEnsVerified, hasLocalNickname: !!model.localNickname, usesDefaultName: model.usesDefaultName, chatType: root.chatType, @@ -196,7 +195,7 @@ Item { ProfileContextMenu { property string pubKey - margins: 8 + margins: Theme.halfPadding onOpenProfileClicked: root.openProfileRequested(pubKey) onCreateOneToOneChat: root.createOneToOneChatRequested(pubKey) diff --git a/ui/app/AppLayouts/Profile/popups/RenameAccountModal.qml b/ui/app/AppLayouts/Profile/popups/RenameAccountModal.qml index 94b43a7fecb..accd1912a4b 100644 --- a/ui/app/AppLayouts/Profile/popups/RenameAccountModal.qml +++ b/ui/app/AppLayouts/Profile/popups/RenameAccountModal.qml @@ -8,7 +8,6 @@ import StatusQ.Controls import StatusQ.Popups import StatusQ.Popups.Dialog import StatusQ.Controls.Validators -import StatusQ.Core.Utils as StatusQUtils import utils @@ -40,8 +39,7 @@ StatusModal { enabled: popup.opened target: emojiPopup function onEmojiSelected(emojiText: string, atCursor: bool) { - let emoji = StatusQUtils.Emoji.deparse(emojiText) - popup.contentItem.accountNameInput.input.asset.emoji = emoji + popup.contentItem.accountNameInput.input.asset.emoji = emojiText } } diff --git a/ui/app/AppLayouts/Profile/views/SyncingView.qml b/ui/app/AppLayouts/Profile/views/SyncingView.qml index bcdd99e30a1..bef616b76a0 100644 --- a/ui/app/AppLayouts/Profile/views/SyncingView.qml +++ b/ui/app/AppLayouts/Profile/views/SyncingView.qml @@ -167,7 +167,7 @@ SettingsContentBase { spacing: Theme.padding StatusBaseText { - Layout.preferredWidth: parent.width - betaTag.width - parent.spacing + Layout.preferredWidth: parent.width - parent.spacing objectName: "syncNewDeviceTextLabel" elide: Text.ElideRight color: Theme.palette.primaryColor1 diff --git a/ui/app/AppLayouts/Wallet/panels/WalletAccountHeader.qml b/ui/app/AppLayouts/Wallet/panels/WalletAccountHeader.qml index fc87a96ae0d..0461a51d63c 100644 --- a/ui/app/AppLayouts/Wallet/panels/WalletAccountHeader.qml +++ b/ui/app/AppLayouts/Wallet/panels/WalletAccountHeader.qml @@ -118,6 +118,11 @@ Control { anchors.margins: 11 emojiId: root.emojiId + + Binding on source { // fallback when we have no emoji + when: root.emojiId === "" + value: Theme.svg("filled-account") + } } } diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index 3fef1c21e77..cd8fdcc210e 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -86,7 +86,7 @@ RightTabBaseView { readonly property var overview: root.walletRootStore.overview allAccounts: overview.isAllAccounts - emojiId: SQUtils.Emoji.iconId(overview.emoji ?? "", SQUtils.Emoji.size.big) + emojiId: SQUtils.Emoji.iconId(overview.emoji ?? "") balance: LocaleUtils.currencyAmountToLocaleString(overview.currencyBalance) balanceLoading: overview.balanceLoading color: Utils.getColorForId(overview.colorId) diff --git a/ui/imports/shared/controls/chat/EmojiReaction.qml b/ui/imports/shared/controls/chat/EmojiReaction.qml index ac1ee13b4d5..b01198ac8c1 100644 --- a/ui/imports/shared/controls/chat/EmojiReaction.qml +++ b/ui/imports/shared/controls/chat/EmojiReaction.qml @@ -26,8 +26,8 @@ Rectangle { StatusEmoji { id: statusEmoji anchors.centerIn: parent - width: Theme.fontSize20 - height: Theme.fontSize20 + width: Theme.fontSize24 + height: Theme.fontSize24 emojiId: root.emojiId } diff --git a/ui/imports/shared/popups/addaccount/states/Main.qml b/ui/imports/shared/popups/addaccount/states/Main.qml index d6f191f6b56..34852e79a76 100644 --- a/ui/imports/shared/popups/addaccount/states/Main.qml +++ b/ui/imports/shared/popups/addaccount/states/Main.qml @@ -150,7 +150,7 @@ Item { } } - onKeyPressed: { + onKeyPressed: event => { root.store.submitPopup(event) } diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml index 8ab4a799b2f..f83d166b879 100644 --- a/ui/imports/shared/views/chat/MessageView.qml +++ b/ui/imports/shared/views/chat/MessageView.qml @@ -180,7 +180,7 @@ Loader { signal emojiReactionToggled(string messageId, string hexcode) - function openProfileContextMenu(sender, isReply = false) { + function openProfileContextMenu(x, y, isReply = false) { if (isViewMemberMessagesePopup) return false @@ -216,13 +216,16 @@ Loader { hasLocalNickname: !!contactDetails.localNickname } - Global.openMenu(profileContextMenuComponent, sender, params) + profileContextMenuComponent.createObject(root, params).popup(x, y) } - function openMessageContextMenu() { + function openMessageContextMenu(x, y) { if (isViewMemberMessagesePopup || placeholderMessage || !root.joined) return + if (root.isChatBlocked && !d.addReactionAllowed) + return + const params = { myPublicKey: userProfile.pubKey, amIChatAdmin: root.amIChatAdmin, @@ -238,7 +241,7 @@ Loader { editRestricted: root.editRestricted, } - Global.openMenu(messageContextMenuComponent, this, params) + messageContextMenuComponent.createObject(root, params).popup(x, y) } function setMessageActive(messageId, active) { @@ -631,10 +634,10 @@ Loader { id: deletedMessage height: 40 Layout.fillWidth: true - Layout.topMargin: 8 - Layout.bottomMargin: 8 - Layout.leftMargin: 16 - spacing: 8 + Layout.topMargin: Theme.halfPadding + Layout.bottomMargin: Theme.halfPadding + Layout.leftMargin: Theme.padding + spacing: Theme.halfPadding readonly property int smartIconSize: 20 readonly property int colorId: Utils.colorIdForPubkey(root.deletedBy) @@ -712,8 +715,8 @@ Loader { StatusDateGroupLabel { id: dateGroupLabel Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 + Layout.topMargin: Theme.padding + Layout.bottomMargin: Theme.padding messageTimestamp: root.messageTimestamp previousMessageTimestamp: root.prevMessageIndex === -1 ? 0 : root.prevMessageTimestamp visible: text !== "" && !root.isInPinnedPopup && !root.isViewMemberMessagesePopup @@ -820,7 +823,7 @@ Loader { d.onImageClicked(image, mouse, imageSource) } - onLinkActivated: { + onLinkActivated: link => { if (link.startsWith(Constants.sendViaChatPrefix)) { const addressOrEns = link.replace(Constants.sendViaChatPrefix, ""); root.sendViaPersonalChatRequested(addressOrEns) @@ -846,10 +849,10 @@ Loader { Global.activateDeepLink(link) } - onProfilePictureClicked: (sender, mouse) => root.openProfileContextMenu(sender) - onReplyProfileClicked: (sender, mouse) => root.openProfileContextMenu(sender, true) + onProfilePictureClicked: (sender, mouse) => root.openProfileContextMenu(mouse.x, mouse.y) + onReplyProfileClicked: (sender, mouse) => root.openProfileContextMenu(mouse.x, mouse.y, true) onReplyMessageClicked: (mouse) => root.messageStore.messageModule.jumpToMessage(root.responseToMessageWithId) - onSenderNameClicked: (sender) => root.openProfileContextMenu(sender) + onSenderNameClicked: (sender) => root.openProfileContextMenu(sender.x, sender.y) onToggleReactionClicked: (hexcode) => { if (root.isChatBlocked) @@ -877,11 +880,7 @@ Loader { mouseArea { acceptedButtons: Qt.RightButton - enabled: (!root.isChatBlocked || d.addReactionAllowed) && - !root.placeholderMessage - onClicked: { - root.openMessageContextMenu() - } + onClicked: mouse => root.openMessageContextMenu(mouse.x, mouse.y) } messageDetails: StatusMessageDetails { @@ -1006,7 +1005,7 @@ Loader { isEdit: true onSendMessage: delegate.editCompletedHandler(editTextInput.getTextWithPublicKeys()) - onOpenGifPopupRequest: root.openGifPopupRequest(params, cbOnGifSelected, cbOnClose) + onOpenGifPopupRequest: (params, cbOnGifSelected, cbOnClose) => root.openGifPopupRequest(params, cbOnGifSelected, cbOnClose) Component.onCompleted: { parseMessage(root.messageText); From a72bf3861a78acb3d0693dfff6db08f71add863f Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Mon, 24 Nov 2025 20:41:04 +0300 Subject: [PATCH 2/3] chore(@e2e): fix emojis path --- test/e2e/gui/screens/messages.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/e2e/gui/screens/messages.py b/test/e2e/gui/screens/messages.py index 23015fcbaa7..dcdd8a7c72d 100644 --- a/test/e2e/gui/screens/messages.py +++ b/test/e2e/gui/screens/messages.py @@ -214,9 +214,22 @@ def get_emoji_reactions_pathes(self): reactions_pathes = [] for child in walk_children(self.object): if getattr(child, 'id', '') == 'reactionDelegate': + # Search for StatusIcon inside reactionDelegate and extract emoji ID from icon path for item in walk_children(child): - if getattr(item, 'objectName', '') == 'emojiReaction': - reactions_pathes.append(item.emojiId) + icon_path = None + if hasattr(item, 'icon'): + icon_path = str(item.icon) + elif hasattr(item, 'source'): + icon_path = str(item.source) + + if icon_path: + # Extract emoji ID from path like "qrc:/assets/twemoji/svg/1f600.svg" + match = re.search(r'/([a-f0-9]+)\.svg', icon_path) + if match: + reactions_pathes.append(match.group(1)) + break + if not reactions_pathes: + raise LookupError('No emoji reactions found for this message') return reactions_pathes From f2a0cace274f653e32c1334de7d526a4cb32b36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Tue, 25 Nov 2025 00:12:59 +0100 Subject: [PATCH 3/3] fix(StatusMessage): bigger emoji reactions + mobile context menu - use StatusTextArea for StatusTextMessage; need this for the context menu on mobile - rework the context menu to ContextMenu + pressAndHold handler (the only combo that works on mobile) - use edge-to-edge separators in the profile context menu --- .../src/StatusQ/Components/StatusMessage.qml | 8 ++------ .../StatusMessageEmojiReactions.qml | 10 +++++++--- .../private/statusMessage/StatusTextMessage.qml | 17 +++++++++++------ .../src/StatusQ/Controls/StatusTextArea.qml | 1 - .../src/StatusQ/Controls/StatusToolTip.qml | 1 + .../shared/controls/chat/EmojiReaction.qml | 4 ++-- ui/imports/shared/status/StatusChatInput.qml | 5 ++++- ui/imports/shared/views/chat/MessageView.qml | 7 +++---- .../shared/views/chat/ProfileContextMenu.qml | 2 ++ 9 files changed, 32 insertions(+), 23 deletions(-) diff --git a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml index 81d56245577..820c0db4e20 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusMessage.qml @@ -45,7 +45,6 @@ Control { property var statusChatInput property alias linksComponent: linksLoader.sourceComponent property alias invitationComponent: invitationBubbleLoader.sourceComponent - property alias mouseArea: mouseArea property string pinnedMsgInfoText: "" @@ -89,6 +88,7 @@ Control { signal senderNameClicked(var sender) signal replyProfileClicked(var sender, var mouse) signal replyMessageClicked(var mouse) + signal pressAndHold(var mouse) signal addReactionClicked(var sender, var mouse) signal toggleReactionClicked(string hexcode) @@ -192,11 +192,6 @@ Control { implicitWidth: messageLayout.implicitWidth implicitHeight: messageLayout.implicitHeight - StatusMouseArea { - id: mouseArea - anchors.fill: parent - } - ColumnLayout { id: messageLayout anchors.fill: parent @@ -293,6 +288,7 @@ Control { textField.onHoveredLinkChanged: { root.hoveredLink = hoveredLink; } + onPressAndHold: mouse => root.pressAndHold(mouse) } } Loader { diff --git a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageEmojiReactions.qml b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageEmojiReactions.qml index 9bb4d06ac84..c1eb8aaf954 100644 --- a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageEmojiReactions.qml +++ b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageEmojiReactions.qml @@ -46,15 +46,19 @@ Flow { Repeater { model: root.reactionsModel - Button { + StatusButton { id: reactionDelegate - verticalPadding: Theme.halfPadding + size: StatusBaseButton.Size.Small + implicitHeight: 32 + + verticalPadding: Theme.halfPadding / 2 leftPadding: Theme.halfPadding rightPadding: Theme.halfPadding / 2 spacing: Theme.halfPadding / 2 background: Rectangle { + implicitWidth: 36 radius: Theme.radius color: { if (reactionDelegate.hovered) { @@ -111,7 +115,7 @@ Flow { // We use a MouseArea because we need to pass the mouse event to the signal StatusMouseArea { anchors.fill: parent - cursorShape: !root.limitReached ? Qt.PointingHandCursor : Qt.ArrowCursor + cursorShape: !root.limitReached ? Qt.PointingHandCursor : Qt.ForbiddenCursor onClicked: (mouse) => { mouse.accepted = true if (root.limitReached) diff --git a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusTextMessage.qml b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusTextMessage.qml index b3956b70fca..10610e9cb8c 100644 --- a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusTextMessage.qml +++ b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusTextMessage.qml @@ -1,4 +1,5 @@ import QtQuick +import QtQuick.Controls import Qt5Compat.GraphicalEffects import StatusQ.Components @@ -30,6 +31,8 @@ Item { implicitWidth: chatText.implicitWidth implicitHeight: chatText.height + d.showMoreHeight / 2 + signal pressAndHold(var mouseEvent) + QtObject { id: d property string hoveredLink: chatText.hoveredLink || root.highlightedLink @@ -86,7 +89,7 @@ Item { color: Theme.palette.baseColor1 } - TextEdit { + StatusTextArea { id: chatText objectName: "StatusTextMessage_chatText" @@ -99,17 +102,18 @@ Item { anchors.leftMargin: d.isQuote ? Theme.halfPadding : 0 anchors.right: parent.right opacity: !showMoreOpacityMask.active && !horizontalOpacityMask.active ? 1 : 0 + background: null + leftPadding: 0 + rightPadding: 0 + topPadding: 0 + bottomPadding: 0 text: d.text selectedTextColor: Theme.palette.directColor1 - selectionColor: Theme.palette.primaryColor3 color: d.isQuote ? Theme.palette.baseColor1 : Theme.palette.directColor1 - font.family: Theme.baseFont.name - font.pixelSize: Theme.primaryTextFontSize textFormat: Text.RichText wrapMode: root.convertToSingleLine ? Text.NoWrap : Text.Wrap readOnly: true - selectByMouse: true // applies to mouse only, not touch - enabled: !Utils.isMobile // eats the touch events, thus breaking the context menu since this is an edit (albeit readonly) + selectByMouse: !Utils.isMobile // applies to mouse only, not touch onLinkActivated: function(link) { if(d.showDisabledTooltipForAddressEnsName(link)) { return @@ -127,6 +131,7 @@ Item { x: hoverHandler.point.position.x - 60 y: -disabledLinkTooltip.height + hoverHandler.point.position.y - 10 } + onPressAndHold: mouseEvent => root.pressAndHold(mouseEvent) } StatusSyntaxHighlighter { diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusTextArea.qml b/ui/StatusQ/src/StatusQ/Controls/StatusTextArea.qml index acd547a0a02..f334cfda2b4 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusTextArea.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusTextArea.qml @@ -71,7 +71,6 @@ TextArea { pixelSize: Theme.primaryTextFontSize } - selectByMouse: true persistentSelection: true wrapMode: TextEdit.WordWrap diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml index 095da79b4c3..6e50a40636a 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml @@ -30,6 +30,7 @@ ToolTip { margins: Theme.halfPadding delay: Utils.isMobile ? Application.styleHints.mousePressAndHoldInterval : 200 + timeout: Utils.isMobile ? 2500 : -1 background: Item { id: statusToolTipBackground diff --git a/ui/imports/shared/controls/chat/EmojiReaction.qml b/ui/imports/shared/controls/chat/EmojiReaction.qml index b01198ac8c1..0a306c3a59d 100644 --- a/ui/imports/shared/controls/chat/EmojiReaction.qml +++ b/ui/imports/shared/controls/chat/EmojiReaction.qml @@ -26,8 +26,8 @@ Rectangle { StatusEmoji { id: statusEmoji anchors.centerIn: parent - width: Theme.fontSize24 - height: Theme.fontSize24 + width: Theme.fontSize23 + height: Theme.fontSize23 emojiId: root.emojiId } diff --git a/ui/imports/shared/status/StatusChatInput.qml b/ui/imports/shared/status/StatusChatInput.qml index d7f4bdfc605..11bd0dd09cc 100644 --- a/ui/imports/shared/status/StatusChatInput.qml +++ b/ui/imports/shared/status/StatusChatInput.qml @@ -1036,7 +1036,10 @@ Rectangle { } } - closeHandler: () => commandBtn.highlighted = false + closeHandler: () => { + commandBtn.highlighted = false + destroy() + } } } diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml index f83d166b879..5016eedc7f8 100644 --- a/ui/imports/shared/views/chat/MessageView.qml +++ b/ui/imports/shared/views/chat/MessageView.qml @@ -1,4 +1,5 @@ import QtQuick +import QtQuick.Controls import QtQuick.Window import QtQuick.Layouts import QtModelsToolkit @@ -878,10 +879,8 @@ Loader { root.messageStore.resendMessage(root.messageId) } - mouseArea { - acceptedButtons: Qt.RightButton - onClicked: mouse => root.openMessageContextMenu(mouse.x, mouse.y) - } + ContextMenu.onRequested: pos => root.openMessageContextMenu(pos.x, pos.y) + onPressAndHold: mouse => root.openMessageContextMenu(mouse.x, mouse.y) messageDetails: StatusMessageDetails { contentType: delegate.contentType diff --git a/ui/imports/shared/views/chat/ProfileContextMenu.qml b/ui/imports/shared/views/chat/ProfileContextMenu.qml index 4c8378b2605..6663b9566fb 100644 --- a/ui/imports/shared/views/chat/ProfileContextMenu.qml +++ b/ui/imports/shared/views/chat/ProfileContextMenu.qml @@ -66,6 +66,7 @@ StatusMenu { StatusMenuSeparator { visible: root.profileType !== Constants.profileType.bridged topPadding: root.topPadding + horizontalPadding: 0 } ViewProfileMenuItem { @@ -129,6 +130,7 @@ StatusMenu { topPadding: root.topPadding visible: root.profileType !== Constants.profileType.bridged && (removeNicknameAction.enabled || unblockAction.enabled || markUntrustworthyMenuItem.enabled || removeUntrustworthyMarkMenuItem.enabled || removeContactAction.enabled || blockMenuItem.enabled) + horizontalPadding: 0 } // Remove Nickname