fix(StatusMessage): Design update and minor improvements (#752)
This commit is contained in:
parent
c922bf5adf
commit
23e50341d2
|
@ -14,9 +14,10 @@ ListView {
|
|||
anchors.fill: parent
|
||||
anchors.margins: 15
|
||||
clip: true
|
||||
|
||||
delegate: StatusMessage {
|
||||
id: delegate
|
||||
width: parent.width
|
||||
width: ListView.view.width
|
||||
|
||||
audioMessageInfoText: "Audio Message"
|
||||
cancelButtonText: "Cancel"
|
||||
|
@ -26,44 +27,70 @@ ListView {
|
|||
resendText: "Resend"
|
||||
pinnedMsgInfoText: "Pinned by"
|
||||
|
||||
timestamp: model.timestamp
|
||||
isAReply: model.isReply
|
||||
hasMention: model.hasMention
|
||||
isPinned: model.isPinned
|
||||
pinnedBy: model.pinnedBy
|
||||
hasExpired: model.hasExpired
|
||||
reactionsModel: model.reactions || []
|
||||
|
||||
messageDetails: StatusMessageDetails {
|
||||
contentType: model.contentType
|
||||
messageContent: model.messageContent
|
||||
amISender: model.amIsender
|
||||
displayName: model.userName
|
||||
secondaryName: model.localName !== "" && model.ensName.startsWith("@") ? model.ensName: ""
|
||||
chatID: model.chatKey
|
||||
profileImage: StatusImageSettings {
|
||||
sender.id: model.senderId
|
||||
sender.userName: model.userName
|
||||
sender.localName: model.localName
|
||||
sender.ensName: model.ensName
|
||||
sender.isContact: model.isContact
|
||||
sender.trustIndicator: model.trustIndicator
|
||||
sender.profileImage {
|
||||
width: 40
|
||||
height: 40
|
||||
source: model.profileImage
|
||||
isIdenticon: model.isIdenticon
|
||||
pubkey: model.senderId
|
||||
source: model.profileImage || ""
|
||||
colorId: 1
|
||||
colorHash: ListModel {
|
||||
ListElement { colorId: 13; segmentLength: 5 }
|
||||
ListElement { colorId: 31; segmentLength: 5 }
|
||||
ListElement { colorId: 10; segmentLength: 1 }
|
||||
ListElement { colorId: 2; segmentLength: 5 }
|
||||
ListElement { colorId: 26; segmentLength: 2 }
|
||||
ListElement { colorId: 19; segmentLength: 4 }
|
||||
ListElement { colorId: 28; segmentLength: 3 }
|
||||
}
|
||||
}
|
||||
|
||||
messageText: model.message
|
||||
hasMention: model.hasMention
|
||||
isContact: model.isContact
|
||||
trustIndicator: model.trustIndicator
|
||||
isPinned: model.isPinned
|
||||
pinnedBy: model.pinnedBy
|
||||
hasExpired: model.hasExpired
|
||||
}
|
||||
timestamp.text: "10:00 am"
|
||||
timestamp.tooltip.text: "10:01 am"
|
||||
// reply related data
|
||||
isAReply: model.isReply
|
||||
|
||||
replyDetails: StatusMessageDetails {
|
||||
amISender: model.isReply ? model.replyAmISender : ""
|
||||
displayName: model.isReply ? model.replySenderName: ""
|
||||
profileImage: StatusImageSettings {
|
||||
amISender: model.isReply && model.replyAmISender
|
||||
sender.id: model.replySenderId || ""
|
||||
sender.userName: model.isReply ? model.replySenderName: ""
|
||||
sender.ensName: model.isReply ? model.replySenderEnsName : ""
|
||||
sender.profileImage {
|
||||
width: 20
|
||||
height: 20
|
||||
pubkey: model.replySenderId
|
||||
source: model.isReply ? model.replyProfileImage: ""
|
||||
isIdenticon: model.isReply ? model.replyIsIdenticon: ""
|
||||
colorId: 1
|
||||
colorHash: ListModel {
|
||||
ListElement { colorId: 13; segmentLength: 5 }
|
||||
ListElement { colorId: 31; segmentLength: 5 }
|
||||
ListElement { colorId: 10; segmentLength: 1 }
|
||||
ListElement { colorId: 2; segmentLength: 5 }
|
||||
ListElement { colorId: 26; segmentLength: 2 }
|
||||
ListElement { colorId: 19; segmentLength: 4 }
|
||||
ListElement { colorId: 28; segmentLength: 3 }
|
||||
}
|
||||
}
|
||||
messageText: model.isReply ? model.replyMessageText: ""
|
||||
contentType: model.replyContentType
|
||||
messageContent: model.replyMessageContent
|
||||
}
|
||||
|
||||
quickActions: [
|
||||
StatusFlatRoundButton {
|
||||
id: emojiBtn
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,54 @@
|
|||
import QtQuick 2.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
StatusBaseText {
|
||||
id: root
|
||||
|
||||
property int previousMessageIndex: -1
|
||||
property double previousMessageTimestamp
|
||||
property double messageTimestamp
|
||||
|
||||
font.pixelSize: 13
|
||||
color: Theme.palette.baseColor1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
text: {
|
||||
if (previousMessageIndex === -1)
|
||||
return "";
|
||||
|
||||
const now = new Date()
|
||||
const yesterday = new Date()
|
||||
yesterday.setDate(now.getDate()-1)
|
||||
|
||||
const currentMsgDate = new Date(messageTimestamp);
|
||||
const prevMsgDate = new Date(previousMessageTimestamp);
|
||||
|
||||
if (!!prevMsgDate && currentMsgDate.getDay() === prevMsgDate.getDay())
|
||||
return "";
|
||||
|
||||
if (now == currentMsgDate)
|
||||
return qsTr("Today");
|
||||
|
||||
if (yesterday == currentMsgDate)
|
||||
return qsTr("Yesterday");
|
||||
|
||||
const monthNames = [
|
||||
qsTr("January"),
|
||||
qsTr("February"),
|
||||
qsTr("March"),
|
||||
qsTr("April"),
|
||||
qsTr("May"),
|
||||
qsTr("June"),
|
||||
qsTr("July"),
|
||||
qsTr("August"),
|
||||
qsTr("September"),
|
||||
qsTr("October"),
|
||||
qsTr("November"),
|
||||
qsTr("December")
|
||||
];
|
||||
|
||||
return monthNames[currentMsgDate.getMonth()] + ", " + currentMsgDate.getDate();
|
||||
}
|
||||
}
|
|
@ -1,14 +1,16 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
import QtQuick.Controls 2.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import "./private/statusMessage"
|
||||
|
||||
Rectangle {
|
||||
id: statusMessage
|
||||
id: root
|
||||
|
||||
enum ContentType {
|
||||
Unknown = 0,
|
||||
|
@ -21,12 +23,12 @@ Rectangle {
|
|||
Invitation = 7
|
||||
}
|
||||
|
||||
property alias messageHeader: messageHeader
|
||||
property alias quickActions:quickActionsPanel.quickActions
|
||||
property alias quickActions: quickActionsPanel.items
|
||||
property alias statusChatInput: editComponent.inputComponent
|
||||
property alias linksComponent: linksLoader.sourceComponent
|
||||
property alias footerComponent: footer.sourceComponent
|
||||
property alias timestamp: messageHeader.timestamp
|
||||
property alias transcationComponent: transactionBubbleLoader.sourceComponent
|
||||
property alias invitationComponent: invitationBubbleLoader.sourceComponent
|
||||
property alias mouseArea: mouseArea
|
||||
|
||||
property string resendText: ""
|
||||
property string cancelButtonText: ""
|
||||
|
@ -35,155 +37,360 @@ Rectangle {
|
|||
property string errorLoadingImageText: ""
|
||||
property string audioMessageInfoText: ""
|
||||
property string pinnedMsgInfoText: ""
|
||||
property var reactionIcons: [
|
||||
Emoji.iconSource("❤"),
|
||||
Emoji.iconSource("👍"),
|
||||
Emoji.iconSource("👎"),
|
||||
Emoji.iconSource("🤣"),
|
||||
Emoji.iconSource("😥"),
|
||||
Emoji.iconSource("😠")
|
||||
]
|
||||
|
||||
property string messageId: ""
|
||||
property bool isAppWindowActive: false
|
||||
property bool editMode: false
|
||||
property bool isAReply: false
|
||||
property bool isEdited: false
|
||||
property bool isChatBlocked: false
|
||||
|
||||
property bool hasMention: false
|
||||
property bool isPinned: false
|
||||
property string pinnedBy: ""
|
||||
property bool hasExpired: false
|
||||
property double timestamp: 0
|
||||
property var reactionsModel: []
|
||||
|
||||
readonly property bool dateGroupVisible: dateGroupLabel.visible
|
||||
property bool showHeader: true
|
||||
property bool isActiveMessage: false
|
||||
property bool disableHover: false
|
||||
property bool hideQuickActions: false
|
||||
property color overrideBackgroundColor: "transparent"
|
||||
property bool overrideBackground: false
|
||||
|
||||
property alias previousMessageIndex: dateGroupLabel.previousMessageIndex
|
||||
property alias previousMessageTimestamp: dateGroupLabel.previousMessageTimestamp
|
||||
|
||||
property StatusMessageDetails messageDetails: StatusMessageDetails {}
|
||||
property StatusMessageDetails replyDetails: StatusMessageDetails {}
|
||||
|
||||
signal profilePictureClicked()
|
||||
signal senderNameClicked()
|
||||
signal editCompleted(var newMsgText)
|
||||
signal replyProfileClicked()
|
||||
signal stickerLoaded()
|
||||
signal imageClicked(var imageSource)
|
||||
property string timestampString: Qt.formatTime(new Date(timestamp), "hh:mm");
|
||||
property string timestampTooltipString: Qt.formatTime(new Date(timestamp), "dddd, MMMM d, yyyy hh:mm:ss t");
|
||||
|
||||
signal clicked(var sender, var mouse)
|
||||
signal profilePictureClicked(var sender, var mouse)
|
||||
signal senderNameClicked(var sender, var mouse)
|
||||
signal replyProfileClicked(var sender, var mouse)
|
||||
|
||||
signal addReactionClicked(var sender, var mouse)
|
||||
signal toggleReactionClicked(int emojiId)
|
||||
signal imageClicked(var image, var mouse, var imageSource)
|
||||
signal stickerClicked()
|
||||
signal resendClicked()
|
||||
|
||||
height: childrenRect.height
|
||||
color: hoverHandler.hovered ? (messageDetails.hasMention ? Theme.palette.mentionColor3 : messageDetails.isPinned ? Theme.palette.pinColor2 : Theme.palette.baseColor2) : messageDetails.hasMention ? Theme.palette.mentionColor4 : messageDetails.isPinned ? Theme.palette.pinColor3 : "transparent"
|
||||
signal editCompleted(var newMsgText)
|
||||
signal editCancelled()
|
||||
signal stickerLoaded()
|
||||
signal linkActivated(string link)
|
||||
|
||||
signal hoverChanged(string messageId, bool hovered)
|
||||
signal activeChanged(string messageId, bool active)
|
||||
|
||||
function startMessageFoundAnimation() {
|
||||
messageFoundAnimation.start();
|
||||
}
|
||||
|
||||
implicitWidth: messageLayout.implicitWidth
|
||||
+ messageLayout.anchors.leftMargin
|
||||
+ messageLayout.anchors.rightMargin
|
||||
|
||||
implicitHeight: messageLayout.implicitHeight
|
||||
+ messageLayout.anchors.topMargin
|
||||
+ messageLayout.anchors.bottomMargin
|
||||
|
||||
color: {
|
||||
if (root.overrideBackground)
|
||||
return root.overrideBackgroundColor;
|
||||
|
||||
if (root.editMode)
|
||||
return Theme.palette.baseColor2;
|
||||
|
||||
if (hoverHandler.hovered || root.isActiveMessage) {
|
||||
if (root.hasMention)
|
||||
return Theme.palette.mentionColor3;
|
||||
if (root.isPinned)
|
||||
return Theme.palette.pinColor2;
|
||||
return Theme.palette.baseColor2;
|
||||
}
|
||||
|
||||
if (root.hasMention)
|
||||
return Theme.palette.mentionColor4;
|
||||
if (root.isPinned)
|
||||
return Theme.palette.pinColor3;
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
}
|
||||
width: 2
|
||||
visible: root.isPinned
|
||||
color: Theme.palette.pinColor1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
}
|
||||
width: 2
|
||||
visible: root.hasMention
|
||||
color: Theme.palette.mentionColor1
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: messageFoundAnimation
|
||||
|
||||
PauseAnimation {
|
||||
duration: 600
|
||||
}
|
||||
NumberAnimation {
|
||||
target: highlightRect
|
||||
property: "opacity"
|
||||
to: 1.0
|
||||
duration: 1500
|
||||
}
|
||||
PauseAnimation {
|
||||
duration: 1000
|
||||
}
|
||||
NumberAnimation {
|
||||
target: highlightRect
|
||||
property: "opacity"
|
||||
to: 0.0
|
||||
duration: 1500
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: highlightRect
|
||||
anchors.fill: parent
|
||||
opacity: 0
|
||||
visible: opacity > 0.001
|
||||
color: Theme.palette.baseColor2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
enabled: !root.isActiveMessage && !root.disableHover
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: messageLayout
|
||||
width: parent.width
|
||||
StatusMessageReply {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 8
|
||||
anchors.bottomMargin: 8
|
||||
|
||||
StatusDateGroupLabel {
|
||||
id: dateGroupLabel
|
||||
Layout.fillWidth: true
|
||||
visible: isAReply
|
||||
replyDetails: statusMessage.replyDetails
|
||||
onReplyProfileClicked: statusMessage.replyProfileClicked()
|
||||
audioMessageInfoText: statusMessage.audioMessageInfoText
|
||||
Layout.topMargin: 20
|
||||
messageTimestamp: root.timestamp
|
||||
visible: text !== ""
|
||||
}
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
active: isAReply
|
||||
visible: active
|
||||
sourceComponent: StatusMessageReply {
|
||||
replyDetails: root.replyDetails
|
||||
onReplyProfileClicked: root.replyProfileClicked(sender, mouse)
|
||||
audioMessageInfoText: root.audioMessageInfoText
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
spacing: 8
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
implicitWidth: profileImage.effectiveSize.width
|
||||
implicitHeight: profileImage.visible ? profileImage.effectiveSize.height : 0
|
||||
|
||||
StatusSmartIdenticon {
|
||||
id: profileImage
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.topMargin: 10
|
||||
Layout.leftMargin: 16
|
||||
image: messageDetails.profileImage
|
||||
name: messageHeader.displayName
|
||||
|
||||
active: root.showHeader
|
||||
visible: active
|
||||
|
||||
name: root.messageDetails.sender.userName
|
||||
image: root.messageDetails.sender.profileImage.imageSettings
|
||||
icon: root.messageDetails.sender.profileImage.iconSettings
|
||||
ringSettings: root.messageDetails.sender.profileImage.ringSettings
|
||||
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
anchors.fill: parent
|
||||
onClicked: statusMessage.profilePictureClicked()
|
||||
onClicked: root.profilePictureClicked(this, mouse)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.topMargin: 10
|
||||
Layout.fillWidth: true
|
||||
StatusPinMessageDetails {
|
||||
visible: messageDetails.isPinned && !editMode
|
||||
pinnedMsgInfoText: statusMessage.pinnedMsgInfoText
|
||||
pinnedBy: messageDetails.pinnedBy
|
||||
|
||||
Loader {
|
||||
active: root.isPinned && !editMode
|
||||
visible: active
|
||||
sourceComponent: StatusPinMessageDetails {
|
||||
pinnedMsgInfoText: root.pinnedMsgInfoText
|
||||
pinnedBy: root.pinnedBy
|
||||
}
|
||||
}
|
||||
StatusMessageHeader {
|
||||
id: messageHeader
|
||||
width: parent.width
|
||||
displayName: messageDetails.displayName
|
||||
secondaryName: messageDetails.secondaryName
|
||||
tertiaryDetail: messageDetails.chatID
|
||||
isContact: messageDetails.isContact
|
||||
trustIndicator: messageDetails.trustIndicator
|
||||
resendText: statusMessage.resendText
|
||||
showResendButton: messageDetails.hasExpired && messageDetails.amISender
|
||||
onClicked: statusMessage.senderNameClicked()
|
||||
onResendClicked: statusMessage.resendClicked()
|
||||
visible: !editMode
|
||||
Layout.fillWidth: true
|
||||
sender: root.messageDetails.sender
|
||||
amISender: root.messageDetails.amISender
|
||||
resendText: root.resendText
|
||||
showResendButton: root.hasExpired && root.messageDetails.amISender
|
||||
onClicked: root.senderNameClicked(sender, mouse)
|
||||
onResendClicked: root.resendClicked()
|
||||
visible: root.showHeader && !editMode
|
||||
timestamp.text: root.timestampString
|
||||
timestamp.tooltip.text: root.timestampTooltipString
|
||||
}
|
||||
Loader {
|
||||
active: !editMode && !!messageDetails.messageText
|
||||
width: parent.width
|
||||
Layout.fillWidth: true
|
||||
active: !editMode && !!root.messageDetails.messageText
|
||||
visible: active
|
||||
sourceComponent: StatusTextMessage {
|
||||
width: parent.width
|
||||
textField.text: messageDetails.messageText
|
||||
textField.text: {
|
||||
if (root.messageDetails.contentType === StatusMessage.ContentType.Sticker)
|
||||
return "";
|
||||
|
||||
const formattedMessage = Utils.linkifyAndXSS(root.messageDetails.messageText);
|
||||
|
||||
if (root.messageDetails.contentType === StatusMessage.ContentType.Emoji)
|
||||
return Emoji.parse(formattedMessage, Emoji.size.middle, Emoji.format.png);
|
||||
|
||||
if (root.isEdited) {
|
||||
const index = formattedMessage.endsWith("code>") ? formattedMessage.length : formattedMessage.length - 4;
|
||||
const editedMessage = formattedMessage.slice(0, index)
|
||||
+ ` <span class="isEdited">` + qsTr("(edited)") + `</span>`
|
||||
+ formattedMessage.slice(index);
|
||||
return Utils.getMessageWithStyle(Emoji.parse(editedMessage), textField.hoveredLink)
|
||||
}
|
||||
|
||||
return Utils.getMessageWithStyle(Emoji.parse(formattedMessage), textField.hoveredLink)
|
||||
}
|
||||
onLinkActivated: {
|
||||
root.linkActivated(link);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: messageDetails.contentType === StatusMessage.ContentType.Image && !editMode
|
||||
active: root.messageDetails.contentType === StatusMessage.ContentType.Image && !editMode
|
||||
visible: active
|
||||
sourceComponent: StatusImageMessage {
|
||||
source: messageDetails.contentType === StatusMessage.ContentType.Image ? messageDetails.messageContent : ""
|
||||
onClicked: statusMessage.imageClicked()
|
||||
shapeType: messageDetails.amISender ? StatusImageMessage.ShapeType.RIGHT_ROUNDED : StatusImageMessage.ShapeType.LEFT_ROUNDED
|
||||
source: root.messageDetails.contentType === StatusMessage.ContentType.Image ? root.messageDetails.messageContent : ""
|
||||
onClicked: root.imageClicked(image, mouse, imageSource)
|
||||
shapeType: root.messageDetails.amISender ? StatusImageMessage.ShapeType.RIGHT_ROUNDED : StatusImageMessage.ShapeType.LEFT_ROUNDED
|
||||
}
|
||||
}
|
||||
StatusSticker {
|
||||
visible: messageDetails.contentType === StatusMessage.ContentType.Sticker && !editMode
|
||||
image.source: messageDetails.messageContent
|
||||
onLoaded: statusMessage.stickerLoaded()
|
||||
}
|
||||
Loader {
|
||||
active: messageDetails.contentType === StatusMessage.ContentType.Audio && !editMode
|
||||
active: root.messageDetails.contentType === StatusMessage.ContentType.Sticker && !editMode
|
||||
visible: active
|
||||
sourceComponent: StatusSticker {
|
||||
image.source: root.messageDetails.messageContent
|
||||
onLoaded: root.stickerLoaded()
|
||||
onClicked: {
|
||||
root.stickerClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
active: root.messageDetails.contentType === StatusMessage.ContentType.Audio && !editMode
|
||||
visible: active
|
||||
sourceComponent: StatusAudioMessage {
|
||||
audioSource: messageDetails.messageContent
|
||||
audioSource: root.messageDetails.messageContent
|
||||
hovered: hoverHandler.hovered
|
||||
audioMessageInfoText: statusMessage.audioMessageInfoText
|
||||
audioMessageInfoText: root.audioMessageInfoText
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
id: linksLoader
|
||||
active: !!linksLoader.sourceComponent
|
||||
active: !root.editMode
|
||||
visible: active
|
||||
}
|
||||
Loader {
|
||||
id: transactionBubbleLoader
|
||||
active: messageDetails.contentType === StatusMessage.ContentType.Transaction && !editMode
|
||||
active: root.messageDetails.contentType === StatusMessage.ContentType.Transaction && !editMode
|
||||
visible: active
|
||||
}
|
||||
Loader {
|
||||
id: invitationBubbleLoader
|
||||
active: messageDetails.contentType === StatusMessage.ContentType.Invitation && !editMode
|
||||
active: root.messageDetails.contentType === StatusMessage.ContentType.Invitation && !editMode
|
||||
visible: active
|
||||
}
|
||||
StatusEditMessage {
|
||||
id: editComponent
|
||||
width: parent.width
|
||||
msgText: messageDetails.messageText
|
||||
visible: editMode
|
||||
saveButtonText: statusMessage.saveButtonText
|
||||
cancelButtonText: statusMessage.cancelButtonText
|
||||
onCancelEditClicked: editMode = false
|
||||
onEditCompleted: {
|
||||
editMode = false
|
||||
statusMessage.editCompleted(newMsgText)
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
active: root.editMode
|
||||
visible: active
|
||||
msgText: root.messageDetails.messageText
|
||||
saveButtonText: root.saveButtonText
|
||||
cancelButtonText: root.cancelButtonText
|
||||
onEditCancelled: root.editCancelled()
|
||||
onEditCompleted: root.editCompleted(newMsgText)
|
||||
}
|
||||
StatusBaseText {
|
||||
id: retryLbl
|
||||
color: Theme.palette.dangerColor1
|
||||
text: statusMessage.resendText
|
||||
text: root.resendText
|
||||
font.pixelSize: 12
|
||||
visible: messageDetails.hasExpired && messageDetails.amISender && !messageDetails.timestamp && !editMode
|
||||
visible: root.hasExpired && root.messageDetails.amISender && !root.timestamp && !editMode
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onClicked: statusMessage.resendClicked()
|
||||
onClicked: root.resendClicked()
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
id: footer
|
||||
active: sourceComponent && !editMode
|
||||
active: root.reactionsModel.count > 0
|
||||
visible: active
|
||||
sourceComponent: StatusMessageEmojiReactions {
|
||||
id: emojiReactionsPanel
|
||||
|
||||
emojiReactionsModel: root.reactionsModel
|
||||
store: root.messageStore
|
||||
icons: root.reactionIcons
|
||||
|
||||
onHoverChanged: {
|
||||
root.hoverChanged(messageId, hovered)
|
||||
}
|
||||
|
||||
isCurrentUser: root.messageDetails.amISender
|
||||
onAddEmojiClicked: root.addReactionClicked(sender, mouse)
|
||||
onToggleReaction: root.toggleReactionClicked(emojiID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,6 +402,6 @@ Rectangle {
|
|||
anchors.rightMargin: 20
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -8
|
||||
visible: hoverHandler.hovered && !editMode
|
||||
visible: hoverHandler.hovered && !root.hideQuickActions
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,24 +6,13 @@ QtObject {
|
|||
id: msgDetails
|
||||
|
||||
property bool amISender: false
|
||||
property string displayName: ""
|
||||
property string secondaryName: ""
|
||||
property string chatID: ""
|
||||
property StatusImageSettings profileImage: StatusImageSettings {
|
||||
width: 40
|
||||
height: 40
|
||||
}
|
||||
|
||||
property StatusMessageSenderDetails sender: StatusMessageSenderDetails { }
|
||||
|
||||
property bool isEdited: false
|
||||
property string messageText: ""
|
||||
property int contentType: 0
|
||||
property string messageText: ""
|
||||
property string messageContent: ""
|
||||
property bool isContact: false
|
||||
property var trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
property bool hasMention: false
|
||||
property bool isPinned: false
|
||||
property string pinnedBy: ""
|
||||
property bool hasExpired: false
|
||||
property string timestamp: ""
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import QtQuick 2.0
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property string id: ""
|
||||
property string userName: ""
|
||||
property string ensName: ""
|
||||
property string localName: ""
|
||||
|
||||
property bool isContact: false
|
||||
property int trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
|
||||
property StatusProfileImageSettings profileImage: StatusProfileImageSettings {
|
||||
pubkey: root.id
|
||||
showRing: !root.ensName
|
||||
width: 40
|
||||
height: 40
|
||||
}
|
||||
|
||||
readonly property string displayName: root.localName !== ""
|
||||
? root.localName
|
||||
: root.ensName !== ""
|
||||
? root.ensName
|
||||
: root.userName
|
||||
|
||||
readonly property string secondaryName: root.localName === ""
|
||||
? ""
|
||||
: root.ensName !== ""
|
||||
? root.ensName
|
||||
: root.userName
|
||||
}
|
|
@ -28,6 +28,10 @@ Loader {
|
|||
distinctiveColors: Theme.palette.identiconRingColors
|
||||
}
|
||||
|
||||
readonly property size effectiveSize: !!statusSmartIdenticon.image.source.toString()
|
||||
? Qt.size(statusSmartIdenticon.image.width, statusSmartIdenticon.image.width)
|
||||
: Qt.size(statusSmartIdenticon.icon.width, statusSmartIdenticon.icon.height)
|
||||
|
||||
sourceComponent: statusSmartIdenticon.icon.isLetterIdenticon ? letterIdenticon :
|
||||
!!statusSmartIdenticon.image.source.toString() ? roundedImage :
|
||||
!!statusSmartIdenticon.icon.name.toString() ? roundedIcon : letterIdenticon
|
||||
|
|
|
@ -6,45 +6,63 @@ import StatusQ.Core.Theme 0.1
|
|||
import StatusQ.Controls 0.1
|
||||
|
||||
Item {
|
||||
id: editText
|
||||
id: root
|
||||
|
||||
property alias inputComponent: chatInputLoader.sourceComponent
|
||||
property alias active: chatInputLoader.active
|
||||
|
||||
property string cancelButtonText: ""
|
||||
property string saveButtonText: ""
|
||||
property string msgText: ""
|
||||
|
||||
signal cancelEditClicked()
|
||||
signal editCancelled()
|
||||
signal editCompleted(var newMsgText)
|
||||
|
||||
height: childrenRect.height
|
||||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: 4
|
||||
|
||||
Loader {
|
||||
id: chatInputLoader
|
||||
// To-Do: Move to StatusChatInput once its moved to StatusQ
|
||||
Layout.fillWidth: true
|
||||
|
||||
/*
|
||||
NOTE: sourceComponent must have `messageText` property
|
||||
TODO: Replace with StatusChatInput once its moved to StatusQ.
|
||||
*/
|
||||
|
||||
sourceComponent: StatusInput {
|
||||
width: editText.width
|
||||
placeholderText: ""
|
||||
readonly property string messageText: input.text
|
||||
width: parent.width
|
||||
input.placeholderText: ""
|
||||
input.text: msgText
|
||||
maximumHeight: 40
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
StatusFlatButton {
|
||||
id: cancelBtn
|
||||
text: cancelButtonText
|
||||
size: StatusBaseButton.Size.Small
|
||||
onClicked: cancelEditClicked()
|
||||
onClicked: {
|
||||
editCancelled()
|
||||
}
|
||||
}
|
||||
StatusButton {
|
||||
id: saveBtn
|
||||
text: saveButtonText
|
||||
size: StatusBaseButton.Size.Small
|
||||
enabled: chatInputLoader.item.input.text.trim().length > 0
|
||||
onClicked: editCompleted(chatInputLoader.item.input.text)
|
||||
enabled: !!chatInputLoader.item && chatInputLoader.item.messageText.trim().length > 0
|
||||
onClicked: {
|
||||
editCompleted(!chatInputLoader.item ? "" : chatInputLoader.item.messageText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,10 @@ Item {
|
|||
property string loadingImageText: ""
|
||||
property string errorLoadingImageText: ""
|
||||
|
||||
signal clicked(var image, var mouse)
|
||||
signal clicked(var image, var mouse, var imageSource)
|
||||
|
||||
width: loadingImage.visible ? loadingImage.width : imageMessage.width
|
||||
height: loadingImage.visible ? loadingImage.height : imageMessage.paintedHeight
|
||||
implicitWidth: loadingImage.visible ? loadingImage.width : imageMessage.width
|
||||
implicitHeight: loadingImage.visible ? loadingImage.height : imageMessage.paintedHeight
|
||||
|
||||
QtObject {
|
||||
id: _internal
|
||||
|
@ -87,7 +87,7 @@ Item {
|
|||
_internal.pausePlaying = ! _internal.pausePlaying
|
||||
return
|
||||
}
|
||||
imageContainer.clicked(imageMessage, mouse)
|
||||
imageContainer.clicked(imageMessage, mouse, imageMessage.source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.13
|
||||
import QtGraphicalEffects 1.13
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitHeight: 22
|
||||
implicitWidth: childrenRect.width
|
||||
|
||||
property int imageMargin: 4
|
||||
signal addEmojiClicked(var sender, var mouse)
|
||||
signal hoverChanged(bool hovered)
|
||||
signal toggleReaction(int emojiID)
|
||||
|
||||
property var store
|
||||
property bool isCurrentUser
|
||||
property var emojiReactionsModel
|
||||
|
||||
property var icons: []
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
function lastTwoItems(nodes) {
|
||||
return nodes.join(qsTr(" and "));
|
||||
}
|
||||
|
||||
function showReactionAuthors(jsonArrayOfUsersReactedWithThisEmoji, emojiId) {
|
||||
const listOfUsers = JSON.parse(jsonArrayOfUsersReactedWithThisEmoji)
|
||||
if (listOfUsers.error) {
|
||||
console.error("error parsing users who reacted to a message, error: ", obj.error)
|
||||
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)]);
|
||||
}
|
||||
|
||||
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(Emoji.getEmojiFromId(emojiId));
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: root.imageMargin
|
||||
|
||||
Repeater {
|
||||
id: reactionRepeater
|
||||
width: childrenRect.width
|
||||
model: root.emojiReactionsModel
|
||||
|
||||
Rectangle {
|
||||
id: emojiContainer
|
||||
|
||||
readonly property bool isHovered: mouseArea.containsMouse
|
||||
|
||||
width: emojiImage.width + emojiCount.width + (root.imageMargin * 2) + + 8
|
||||
height: 20
|
||||
radius: 10
|
||||
color: model.didIReactWithThisEmoji ?
|
||||
(isHovered ? Theme.palette.statusMessage.emojiReactionActiveBackgroundHovered : Theme.palette.statusMessage.emojiReactionActiveBackground) :
|
||||
(isHovered ? Theme.palette.statusMessage.emojiReactionBackgroundHovered : Theme.palette.statusMessage.emojiReactionBackground)
|
||||
|
||||
StatusToolTip {
|
||||
visible: mouseArea.containsMouse
|
||||
maxWidth: 400
|
||||
text: d.showReactionAuthors(model.jsonArrayOfUsersReactedWithThisEmoji, model.emojiId)
|
||||
}
|
||||
|
||||
// Rounded corner to cover one corner
|
||||
Rectangle {
|
||||
color: parent.color
|
||||
width: 10
|
||||
height: 10
|
||||
anchors.top: parent.top
|
||||
anchors.left: !root.isCurrentUser ? parent.left : undefined
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: !root.isCurrentUser ? undefined : parent.right
|
||||
anchors.rightMargin: 0
|
||||
radius: 2
|
||||
z: -1
|
||||
}
|
||||
|
||||
// This is a workaround to get a "border" around the rectangle including the weird rectangle
|
||||
Loader {
|
||||
active: model.didIReactWithThisEmoji
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -1
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -1
|
||||
z: -2
|
||||
|
||||
sourceComponent: Component {
|
||||
Rectangle {
|
||||
width: emojiContainer.width + 2
|
||||
height: emojiContainer.height + 2
|
||||
radius: emojiContainer.radius
|
||||
color: Theme.palette.primaryColor1
|
||||
|
||||
Rectangle {
|
||||
color: parent.color
|
||||
width: 10
|
||||
height: 10
|
||||
anchors.top: parent.top
|
||||
anchors.left: !root.isCurrentUser ? parent.left : undefined
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: !root.isCurrentUser ? undefined : parent.right
|
||||
anchors.rightMargin: 0
|
||||
radius: 2
|
||||
z: -1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use Row
|
||||
|
||||
StatusEmoji {
|
||||
id: emojiImage
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.imageMargin
|
||||
|
||||
width: 15
|
||||
height: 15
|
||||
|
||||
source: {
|
||||
if (model.emojiId >= 1 && model.emojiId <= root.icons.length)
|
||||
return root.icons[model.emojiId - 1];
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: emojiCount
|
||||
text: model.numberOfReactions
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: emojiImage.right
|
||||
anchors.leftMargin: root.imageMargin
|
||||
font.pixelSize: 12
|
||||
color: model.didIReactWithThisEmoji ? Theme.palette.primaryColor1 : Theme.palette.directColor1
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
root.hoverChanged(true)
|
||||
}
|
||||
onExited: {
|
||||
root.hoverChanged(false)
|
||||
}
|
||||
onClicked: {
|
||||
root.toggleReaction(model.emojiId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: addEmojiButton.width + addEmojiButton.anchors.leftMargin // there is more margin between the button and the emojis than between each emoji
|
||||
height: addEmojiButton.height
|
||||
|
||||
StatusIcon {
|
||||
id: addEmojiButton
|
||||
|
||||
property bool isHovered: false // TODO: Replace with mouseArea.containsMouse
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2.5
|
||||
|
||||
icon: "reaction-b"
|
||||
width: 16.5
|
||||
height: 16.5
|
||||
|
||||
color: addEmojiButton.isHovered ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: addEmojiButtonMouseArea
|
||||
anchors.fill: addEmojiButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onEntered: addEmojiButton.isHovered = true
|
||||
onExited: addEmojiButton.isHovered = false
|
||||
onClicked: {
|
||||
root.addEmojiClicked(this, mouse);
|
||||
}
|
||||
}
|
||||
|
||||
StatusToolTip {
|
||||
visible: addEmojiButton.isHovered
|
||||
text: qsTr("Add reaction")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,36 +3,39 @@ import QtQuick.Layouts 1.14
|
|||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
Item {
|
||||
id: statusMessageHeader
|
||||
id: root
|
||||
|
||||
property StatusMessageSenderDetails sender: StatusMessageSenderDetails { }
|
||||
|
||||
property alias displayNameLabel: primaryDisplayName
|
||||
property alias secondaryNameLabel: secondaryDisplayName
|
||||
property alias tertiaryDetailsLabel: tertiaryDetailText
|
||||
property alias timestamp: timestampText
|
||||
|
||||
property string displayName: ""
|
||||
property string secondaryName: ""
|
||||
property string tertiaryDetail: ""
|
||||
property string tertiaryDetail: sender.id
|
||||
property string resendText: ""
|
||||
property bool showResendButton: false
|
||||
property bool isContact: false
|
||||
property var trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
property bool isContact: sender.isContact
|
||||
property int trustIndicator: sender.trustIndicator
|
||||
property bool amISender: false
|
||||
|
||||
signal clicked()
|
||||
signal clicked(var sender, var mouse)
|
||||
signal resendClicked()
|
||||
|
||||
height: childrenRect.height
|
||||
width: primaryDisplayName.width + (secondaryDisplayName.visible ? secondaryDisplayName.width + header.spacing : 0)
|
||||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
|
||||
RowLayout {
|
||||
id: header
|
||||
id: layout
|
||||
spacing: 4
|
||||
TextEdit {
|
||||
id: primaryDisplayName
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
font.family: Theme.palette.baseFont.name
|
||||
font.weight: Font.Medium
|
||||
font.pixelSize: 15
|
||||
|
@ -41,7 +44,7 @@ Item {
|
|||
wrapMode: Text.WordWrap
|
||||
selectByMouse: true
|
||||
color: Theme.palette.primaryColor1
|
||||
text: displayName
|
||||
text: root.amISender ? qsTr("You") : root.sender.displayName
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
|
@ -49,47 +52,45 @@ Item {
|
|||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
statusMessageHeader.clicked()
|
||||
root.clicked(this, mouse)
|
||||
}
|
||||
}
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
}
|
||||
StatusContactVerificationIcons {
|
||||
isContact: statusMessageHeader.isContact
|
||||
trustIndicator: statusMessageHeader.trustIndicator
|
||||
visible: !root.amISender
|
||||
isContact: root.isContact
|
||||
trustIndicator: root.trustIndicator
|
||||
}
|
||||
StatusBaseText {
|
||||
id: secondaryDisplayName
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: !root.amISender && !!root.sender.secondaryName
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 10
|
||||
text: secondaryName
|
||||
visible: !!text
|
||||
text: `(${root.sender.secondaryName})`
|
||||
}
|
||||
StatusBaseText {
|
||||
id: dotSeparator1
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: secondaryDisplayName.visible
|
||||
font.pixelSize: 10
|
||||
color: Theme.palette.baseColor1
|
||||
text: "."
|
||||
visible: secondaryDisplayName.visible
|
||||
text: "•"
|
||||
}
|
||||
StatusBaseText {
|
||||
id: tertiaryDetailText
|
||||
visible: !root.amISender
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.maximumWidth: 58
|
||||
font.pixelSize: 10
|
||||
elide: Text.ElideMiddle
|
||||
color: Theme.palette.baseColor1
|
||||
text: tertiaryDetail
|
||||
text: Utils.elideText(tertiaryDetail, 5, 3)
|
||||
}
|
||||
StatusBaseText {
|
||||
id: dotSeparator2
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: tertiaryDetailText.visible
|
||||
font.pixelSize: 10
|
||||
color: Theme.palette.baseColor1
|
||||
text: "."
|
||||
visible: tertiaryDetailText.visible
|
||||
text: "•"
|
||||
}
|
||||
StatusTimeStampLabel {
|
||||
id: timestampText
|
||||
|
@ -98,12 +99,12 @@ Item {
|
|||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Theme.palette.dangerColor1
|
||||
font.pixelSize: 12
|
||||
text: statusMessageHeader.resendText
|
||||
text: root.resendText
|
||||
visible: showResendButton && !!timestampText.text
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onClicked: statusMessageHeader.resendClicked()
|
||||
onClicked: root.resendClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,29 +6,29 @@ import StatusQ.Controls 0.1
|
|||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
Rectangle {
|
||||
id: buttonsContainer
|
||||
id: root
|
||||
|
||||
property list<Item> quickActions
|
||||
property list<Item> items
|
||||
|
||||
QtObject {
|
||||
id: _internal
|
||||
readonly property int containerMargin: 2
|
||||
}
|
||||
|
||||
width: buttonRow.width + _internal.containerMargin * 2
|
||||
height: 36
|
||||
implicitWidth: buttonRow.width + _internal.containerMargin * 2
|
||||
implicitHeight: 36
|
||||
radius: 8
|
||||
color: Theme.palette.statusSelect.menuItemBackgroundColor
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
width: buttonsContainer.width
|
||||
height: buttonsContainer.height
|
||||
x: buttonsContainer.x
|
||||
y: buttonsContainer.y + 10
|
||||
width: root.width
|
||||
height: root.height
|
||||
x: root.x
|
||||
y: root.y + 10
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 2
|
||||
source: buttonsContainer
|
||||
source: root
|
||||
radius: 10
|
||||
samples: 15
|
||||
color: Theme.palette.dropShadow
|
||||
|
@ -39,13 +39,13 @@ Rectangle {
|
|||
spacing: _internal.containerMargin
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: _internal.containerMargin
|
||||
anchors.verticalCenter: buttonsContainer.verticalCenter
|
||||
anchors.verticalCenter: root.verticalCenter
|
||||
height: parent.height - 2 * _internal.containerMargin
|
||||
}
|
||||
|
||||
onQuickActionsChanged: {
|
||||
for (let idx in quickActions) {
|
||||
quickActions[idx].parent = buttonRow
|
||||
onItemsChanged: {
|
||||
for (let idx in items) {
|
||||
items[idx].parent = buttonRow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,18 +7,19 @@ import StatusQ.Core 0.1
|
|||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
Loader {
|
||||
id: chatReply
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property StatusMessageDetails replyDetails
|
||||
property string audioMessageInfoText: ""
|
||||
|
||||
signal replyProfileClicked()
|
||||
signal replyProfileClicked(var sender, var mouse)
|
||||
|
||||
active: visible
|
||||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
|
||||
sourceComponent: RowLayout {
|
||||
id: replyLayout
|
||||
RowLayout {
|
||||
id: layout
|
||||
spacing: 8
|
||||
Shape {
|
||||
id: replyCorner
|
||||
|
@ -56,13 +57,16 @@ Loader {
|
|||
StatusSmartIdenticon {
|
||||
id: profileImage
|
||||
Layout.alignment: Qt.AlignTop
|
||||
image: replyDetails.profileImage
|
||||
name: replyDetails.displayName
|
||||
name: replyDetails.sender.userName
|
||||
image: replyDetails.sender.profileImage.imageSettings
|
||||
icon: replyDetails.sender.profileImage.iconSettings
|
||||
ringSettings: replyDetails.sender.profileImage.ringSettings
|
||||
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
anchors.fill: parent
|
||||
onClicked: replyProfileClicked()
|
||||
onClicked: replyProfileClicked(this, mouse)
|
||||
}
|
||||
}
|
||||
TextEdit {
|
||||
|
@ -74,7 +78,7 @@ Loader {
|
|||
font.weight: Font.Medium
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
text: replyDetails.displayName
|
||||
text: replyDetails.amISender ? qsTr("You") : replyDetails.sender.displayName
|
||||
}
|
||||
}
|
||||
StatusTextMessage {
|
||||
|
@ -85,13 +89,14 @@ Loader {
|
|||
textField.height: 18
|
||||
clip: true
|
||||
visible: !!replyDetails.messageText
|
||||
allowShowMore: false
|
||||
}
|
||||
StatusImageMessage {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: imageAlias.paintedHeight
|
||||
imageWidth: 56
|
||||
source: replyDetails.contentType === StatusMessage.ContentType.Image ? replyDetails.messageContent : ""
|
||||
visible: replyDetails.contentType === StatusMessage.ContentType.Image
|
||||
// visible: replyDetails.contentType === StatusMessage.ContentType.Image
|
||||
shapeType: StatusImageMessage.ShapeType.ROUNDED
|
||||
}
|
||||
Item {
|
||||
|
@ -116,7 +121,7 @@ Loader {
|
|||
height: 22
|
||||
isPreview: true
|
||||
audioSource: replyDetails.messageContent
|
||||
audioMessageInfoText: chatReply.audioMessageInfoText
|
||||
audioMessageInfoText: root.audioMessageInfoText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
import QtGraphicalEffects 1.13
|
||||
|
||||
|
@ -11,14 +12,34 @@ Loader {
|
|||
|
||||
active: visible
|
||||
|
||||
sourceComponent: Rectangle {
|
||||
height: 24
|
||||
width: layout.width + 16
|
||||
color: Theme.palette.pinColor2
|
||||
sourceComponent: Control {
|
||||
verticalPadding: 3
|
||||
leftPadding: 2
|
||||
rightPadding: 6
|
||||
|
||||
background: Rectangle {
|
||||
readonly property color translucentColor: Theme.palette.pinColor2
|
||||
|
||||
implicitWidth: 24
|
||||
implicitHeight: 24
|
||||
color: Qt.rgba(translucentColor.r,
|
||||
translucentColor.g,
|
||||
translucentColor.b, 1)
|
||||
opacity: translucentColor.a
|
||||
layer.enabled: true
|
||||
radius: 12
|
||||
RowLayout {
|
||||
id: layout
|
||||
anchors.centerIn: parent
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
width: parent.width / 2
|
||||
height: parent.height / 2
|
||||
color: parent.color
|
||||
radius: 4
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
StatusIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 16
|
||||
|
|
|
@ -6,24 +6,33 @@ import StatusQ.Controls 0.1
|
|||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
Item {
|
||||
id: textMessage
|
||||
id: root
|
||||
|
||||
property int contentType: 0
|
||||
property alias textField: chatText
|
||||
property bool allowShowMore: true
|
||||
|
||||
signal linkActivated(url link)
|
||||
signal linkActivated(string link)
|
||||
|
||||
implicitHeight: showMoreLoader.active ? childrenRect.height : chatText.height
|
||||
implicitWidth: chatText.implicitWidth
|
||||
implicitHeight: chatText.effectiveHeight + d.showMoreHeight
|
||||
|
||||
QtObject {
|
||||
id: _internal
|
||||
id: d
|
||||
property bool readMore: false
|
||||
property bool veryLongChatText: chatText.length > 1000
|
||||
readonly property bool veryLongChatText: chatText.length > 1000
|
||||
readonly property int showMoreHeight: showMoreLoader.visible ? showMoreLoader.height : 0
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: chatText
|
||||
visible: !showMoreLoader.active || _internal.readMore
|
||||
|
||||
readonly property int effectiveHeight: d.veryLongChatText && !d.readMore ? Math.min(chatText.implicitHeight, 200)
|
||||
: chatText.implicitHeight
|
||||
|
||||
width: parent.width
|
||||
height: effectiveHeight + d.showMoreHeight / 2
|
||||
visible: !opMask.active
|
||||
clip: true
|
||||
selectedTextColor: Theme.palette.directColor1
|
||||
selectionColor: Theme.palette.primaryColor3
|
||||
color: Theme.palette.directColor1
|
||||
|
@ -33,12 +42,12 @@ Item {
|
|||
wrapMode: Text.Wrap
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
height: _internal.veryLongChatText && !_internal.readMore ? Math.min(implicitHeight, 200) : implicitHeight
|
||||
width: parent.width
|
||||
clip: height < implicitHeight
|
||||
onLinkActivated: textMessage.linkActivated(link)
|
||||
onLinkActivated: {
|
||||
root.linkActivated(link);
|
||||
}
|
||||
onLinkHovered: {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
// Strange thing. Without this empty stub the cursorShape
|
||||
// is not changed to pointingHandCursor.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +69,7 @@ Item {
|
|||
|
||||
Loader {
|
||||
id: opMask
|
||||
active: showMoreLoader.active && !_internal.readMore
|
||||
active: showMoreLoader.active && !d.readMore
|
||||
anchors.fill: chatText
|
||||
sourceComponent: OpacityMask {
|
||||
source: chatText
|
||||
|
@ -70,17 +79,17 @@ Item {
|
|||
|
||||
Loader {
|
||||
id: showMoreLoader
|
||||
active: _internal.veryLongChatText
|
||||
anchors.top: chatText.bottom
|
||||
anchors.topMargin: -10
|
||||
active: root.allowShowMore && d.veryLongChatText
|
||||
visible: active
|
||||
anchors.verticalCenter: chatText.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
sourceComponent: StatusRoundButton {
|
||||
implicitWidth: 24
|
||||
implicitHeight: 24
|
||||
type: StatusRoundButton.Type.Secondary
|
||||
icon.name: _internal.readMore ? "chevron-up": "chevron-down"
|
||||
icon.name: d.readMore ? "chevron-up": "chevron-down"
|
||||
onClicked: {
|
||||
_internal.readMore = !_internal.readMore
|
||||
d.readMore = !d.readMore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ StatusChatToolBar 0.1 StatusChatToolBar.qml
|
|||
StatusContactRequestsIndicatorListItem 0.1 StatusContactRequestsIndicatorListItem.qml
|
||||
StatusEmoji 0.1 StatusEmoji.qml
|
||||
StatusContactVerificationIcons 0.1 StatusContactVerificationIcons.qml
|
||||
StatusDateGroupLabel 0.1 StatusDateGroupLabel.qml
|
||||
StatusDescriptionListItem 0.1 StatusDescriptionListItem.qml
|
||||
StatusLetterIdenticon 0.1 StatusLetterIdenticon.qml
|
||||
StatusListItem 0.1 StatusListItem.qml
|
||||
|
@ -31,6 +32,7 @@ StatusExpandableItem 0.1 StatusExpandableItem.qml
|
|||
StatusSmartIdenticon 0.1 StatusSmartIdenticon.qml
|
||||
StatusMessage 0.1 StatusMessage.qml
|
||||
StatusMessageDetails 0.1 StatusMessageDetails.qml
|
||||
StatusMessageSenderDetails 0.1 StatusMessageSenderDetails.qml
|
||||
StatusTagSelector 0.1 StatusTagSelector.qml
|
||||
StatusToastMessage 0.1 StatusToastMessage.qml
|
||||
StatusWizardStepper 0.1 StatusWizardStepper.qml
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import QtQuick 2.0
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property url source
|
||||
property int width
|
||||
property int height
|
||||
property bool isIdenticon: false
|
||||
|
||||
property string name
|
||||
property string pubkey
|
||||
property string image
|
||||
property bool showRing: true
|
||||
property bool interactive: true
|
||||
|
||||
property int colorId // TODO: default value Utils.colorIdForPubkey(pubkey)
|
||||
property var colorHash // TODO: default value Utils.getColorHashAsJson(pubkey)
|
||||
|
||||
property StatusImageSettings imageSettings: StatusImageSettings {
|
||||
width: root.width
|
||||
height: root.height
|
||||
source: root.source
|
||||
}
|
||||
|
||||
readonly property StatusIconSettings iconSettings: StatusIconSettings {
|
||||
width: root.width
|
||||
height: root.height
|
||||
color: Theme.palette.userCustomizationColors[root.colorId]
|
||||
charactersLen: 2
|
||||
}
|
||||
|
||||
readonly property StatusIdenticonRingSettings ringSettings: StatusIdenticonRingSettings {
|
||||
initalAngleRad: 0
|
||||
ringPxSize: Math.max(1.5, root.width / 24.0)
|
||||
ringSpecModel: root.showRing ? root.colorHash : undefined
|
||||
distinctiveColors: Theme.palette.identiconRingColors
|
||||
}
|
||||
}
|
|
@ -154,5 +154,12 @@ ThemePalette {
|
|||
property color menuItemBackgroundColor: baseColor2
|
||||
property color menuItemHoverBackgroundColor: directColor7
|
||||
}
|
||||
|
||||
property QtObject statusMessage: QtObject {
|
||||
property color emojiReactionBackground: "#2d2823"
|
||||
property color emojiReactionBackgroundHovered: "#3a3632"
|
||||
property color emojiReactionActiveBackground: "#353a4d"
|
||||
property color emojiReactionActiveBackgroundHovered: "#cbd5f1"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -152,5 +152,12 @@ ThemePalette {
|
|||
property color menuItemBackgroundColor: white
|
||||
property color menuItemHoverBackgroundColor: baseColor2
|
||||
}
|
||||
|
||||
property QtObject statusMessage: QtObject {
|
||||
property color emojiReactionBackground: "#e2e6e9"
|
||||
property color emojiReactionBackgroundHovered: "#d7dadd"
|
||||
property color emojiReactionActiveBackground: getColor('blue6')
|
||||
property color emojiReactionActiveBackgroundHovered: "#cbd5f1"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -243,7 +243,8 @@ QtObject {
|
|||
}
|
||||
|
||||
function getColor(name, alpha) {
|
||||
return !!alpha ? alphaColor(StatusColors.colors[name], alpha) : StatusColors.colors[name]
|
||||
return !!alpha ? alphaColor(StatusColors.colors[name], alpha)
|
||||
: StatusColors.colors[name]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick 2.13
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import "./xss.js" as XSS
|
||||
|
||||
QtObject {
|
||||
|
||||
|
@ -151,6 +153,71 @@ QtObject {
|
|||
}
|
||||
}
|
||||
|
||||
function linkifyAndXSS(inputText) {
|
||||
//URLs starting with http://, https://, or ftp://
|
||||
var replacePattern1 = /(\b(https?|ftp|statusim):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
|
||||
var replacedText = inputText.replace(replacePattern1, "<a href='$1'>$1</a>");
|
||||
|
||||
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
|
||||
var replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
|
||||
replacedText = replacedText.replace(replacePattern2, "$1<a href='http://$2'>$2</a>");
|
||||
|
||||
return XSS.filterXSS(replacedText)
|
||||
}
|
||||
|
||||
function filterXSS(inputText) {
|
||||
return XSS.filterXSS(inputText)
|
||||
}
|
||||
|
||||
function getMessageWithStyle(msg, hoveredLink = "") {
|
||||
return `<style type="text/css">` +
|
||||
`img, a, del, code, blockquote { margin: 0; padding: 0; }` +
|
||||
`code {` +
|
||||
`font-family: ${Theme.palette.codeFont.name};` +
|
||||
`font-weight: 400;` +
|
||||
`font-size: 14;` +
|
||||
`padding: 2px 4px;` +
|
||||
`border-radius: 4px;` +
|
||||
`background-color: ${Theme.palette.baseColor2};` +
|
||||
`color: ${Theme.palette.directColor1};` +
|
||||
`white-space: pre;` +
|
||||
`}` +
|
||||
`p {` +
|
||||
`line-height: 22px;` +
|
||||
`}` +
|
||||
`a {` +
|
||||
`color: ${Theme.palette.primaryColor1};` +
|
||||
`}` +
|
||||
`a.mention {` +
|
||||
`color: ${Theme.palette.mentionColor1};` +
|
||||
`background-color: ${Theme.palette.mentionColor4};` +
|
||||
`text-decoration: none;` +
|
||||
`padding: 0px 2px;` +
|
||||
`}` +
|
||||
(hoveredLink !== "" ? `a.mention[href="${hoveredLink}"] { background-color: ${Theme.palette.mentionColor2}; }` : ``) +
|
||||
`del {` +
|
||||
`text-decoration: line-through;` +
|
||||
`}` +
|
||||
`table.blockquote td {` +
|
||||
`padding-left: 10px;` +
|
||||
`color: ${Theme.palette.baseColor1};` +
|
||||
`}` +
|
||||
`table.blockquote td.quoteline {` +
|
||||
`background-color: ${Theme.palette.baseColor1};` +
|
||||
`height: 100%;` +
|
||||
`padding-left: 0;` +
|
||||
`}` +
|
||||
`.emoji {` +
|
||||
`vertical-align: bottom;` +
|
||||
`}` +
|
||||
`span.isEdited {` +
|
||||
`color: ${Theme.palette.baseColor1};` +
|
||||
`margin-left: 5px` +
|
||||
`}` +
|
||||
`</style>` +
|
||||
`${msg}`
|
||||
}
|
||||
|
||||
function delegateModelSort(srcGroup, dstGroup, lessThan) {
|
||||
const insertPosition = (lessThan, item) => {
|
||||
let lower = 0
|
||||
|
@ -173,6 +240,10 @@ QtObject {
|
|||
dstGroup.move(item.itemsIndex, index)
|
||||
}
|
||||
}
|
||||
|
||||
function elideText(text, leftCharsCount, rightCharsCount = leftCharsCount) {
|
||||
return text.substr(0, leftCharsCount) + "..." + text.substr(text.length - rightCharsCount)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
module StatusQ.Core.Utils
|
||||
|
||||
EmojiJSON 1.0 emojiList.js
|
||||
XSS 1.0 xss.js
|
||||
singleton Utils 0.1 Utils.qml
|
||||
singleton Emoji 0.1 Emoji.qml
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -14,3 +14,4 @@ StatusAnimatedStack 0.1 StatusAnimatedStack.qml
|
|||
StatusScrollView 0.1 StatusScrollView.qml
|
||||
StatusListView 0.1 StatusListView.qml
|
||||
StatusGridView 0.1 StatusGridView.qml
|
||||
StatusProfileImageSettings 0.1 StatusProfileImageSettings.qml
|
||||
|
|
Loading…
Reference in New Issue