fix(StatusMessage): Design update and minor improvements (#752)

This commit is contained in:
Igor Sirotin 2022-08-20 03:01:28 +03:00 committed by Michał Cieślak
parent c922bf5adf
commit 23e50341d2
24 changed files with 2800 additions and 546 deletions

View File

@ -14,9 +14,10 @@ ListView {
anchors.fill: parent anchors.fill: parent
anchors.margins: 15 anchors.margins: 15
clip: true clip: true
delegate: StatusMessage { delegate: StatusMessage {
id: delegate id: delegate
width: parent.width width: ListView.view.width
audioMessageInfoText: "Audio Message" audioMessageInfoText: "Audio Message"
cancelButtonText: "Cancel" cancelButtonText: "Cancel"
@ -26,44 +27,70 @@ ListView {
resendText: "Resend" resendText: "Resend"
pinnedMsgInfoText: "Pinned by" 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 { messageDetails: StatusMessageDetails {
contentType: model.contentType contentType: model.contentType
messageContent: model.messageContent messageContent: model.messageContent
amISender: model.amIsender amISender: model.amIsender
displayName: model.userName sender.id: model.senderId
secondaryName: model.localName !== "" && model.ensName.startsWith("@") ? model.ensName: "" sender.userName: model.userName
chatID: model.chatKey sender.localName: model.localName
profileImage: StatusImageSettings { sender.ensName: model.ensName
sender.isContact: model.isContact
sender.trustIndicator: model.trustIndicator
sender.profileImage {
width: 40 width: 40
height: 40 height: 40
source: model.profileImage pubkey: model.senderId
isIdenticon: model.isIdenticon 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 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 { replyDetails: StatusMessageDetails {
amISender: model.isReply ? model.replyAmISender : "" amISender: model.isReply && model.replyAmISender
displayName: model.isReply ? model.replySenderName: "" sender.id: model.replySenderId || ""
profileImage: StatusImageSettings { sender.userName: model.isReply ? model.replySenderName: ""
sender.ensName: model.isReply ? model.replySenderEnsName : ""
sender.profileImage {
width: 20 width: 20
height: 20 height: 20
pubkey: model.replySenderId
source: model.isReply ? model.replyProfileImage: "" 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: "" messageText: model.isReply ? model.replyMessageText: ""
contentType: model.replyContentType contentType: model.replyContentType
messageContent: model.replyMessageContent messageContent: model.replyMessageContent
} }
quickActions: [ quickActions: [
StatusFlatRoundButton { StatusFlatRoundButton {
id: emojiBtn id: emojiBtn

File diff suppressed because one or more lines are too long

View File

@ -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();
}
}

View File

@ -1,14 +1,16 @@
import QtQuick 2.14 import QtQuick 2.14
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import "./private/statusMessage" import "./private/statusMessage"
Rectangle { Rectangle {
id: statusMessage id: root
enum ContentType { enum ContentType {
Unknown = 0, Unknown = 0,
@ -21,12 +23,12 @@ Rectangle {
Invitation = 7 Invitation = 7
} }
property alias messageHeader: messageHeader property alias quickActions: quickActionsPanel.items
property alias quickActions:quickActionsPanel.quickActions
property alias statusChatInput: editComponent.inputComponent property alias statusChatInput: editComponent.inputComponent
property alias linksComponent: linksLoader.sourceComponent property alias linksComponent: linksLoader.sourceComponent
property alias footerComponent: footer.sourceComponent property alias transcationComponent: transactionBubbleLoader.sourceComponent
property alias timestamp: messageHeader.timestamp property alias invitationComponent: invitationBubbleLoader.sourceComponent
property alias mouseArea: mouseArea
property string resendText: "" property string resendText: ""
property string cancelButtonText: "" property string cancelButtonText: ""
@ -35,155 +37,360 @@ Rectangle {
property string errorLoadingImageText: "" property string errorLoadingImageText: ""
property string audioMessageInfoText: "" property string audioMessageInfoText: ""
property string pinnedMsgInfoText: "" 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 isAppWindowActive: false
property bool editMode: false property bool editMode: false
property bool isAReply: 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 messageDetails: StatusMessageDetails {}
property StatusMessageDetails replyDetails: StatusMessageDetails {} property StatusMessageDetails replyDetails: StatusMessageDetails {}
signal profilePictureClicked() property string timestampString: Qt.formatTime(new Date(timestamp), "hh:mm");
signal senderNameClicked() property string timestampTooltipString: Qt.formatTime(new Date(timestamp), "dddd, MMMM d, yyyy hh:mm:ss t");
signal editCompleted(var newMsgText)
signal replyProfileClicked() signal clicked(var sender, var mouse)
signal stickerLoaded() signal profilePictureClicked(var sender, var mouse)
signal imageClicked(var imageSource) 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() signal resendClicked()
height: childrenRect.height signal editCompleted(var newMsgText)
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 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 { HoverHandler {
id: hoverHandler id: hoverHandler
enabled: !root.isActiveMessage && !root.disableHover
} }
ColumnLayout { ColumnLayout {
id: messageLayout id: messageLayout
width: parent.width anchors.fill: parent
StatusMessageReply { anchors.topMargin: 8
anchors.bottomMargin: 8
StatusDateGroupLabel {
id: dateGroupLabel
Layout.fillWidth: true Layout.fillWidth: true
visible: isAReply Layout.topMargin: 20
replyDetails: statusMessage.replyDetails messageTimestamp: root.timestamp
onReplyProfileClicked: statusMessage.replyProfileClicked() visible: text !== ""
audioMessageInfoText: statusMessage.audioMessageInfoText
} }
RowLayout {
spacing: 8 Loader {
Layout.fillWidth: true Layout.fillWidth: true
StatusSmartIdenticon { active: isAReply
id: profileImage 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 Layout.alignment: Qt.AlignTop
Layout.topMargin: 10
Layout.leftMargin: 16 implicitWidth: profileImage.effectiveSize.width
image: messageDetails.profileImage implicitHeight: profileImage.visible ? profileImage.effectiveSize.height : 0
name: messageHeader.displayName
MouseArea { StatusSmartIdenticon {
cursorShape: Qt.PointingHandCursor id: profileImage
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent active: root.showHeader
onClicked: statusMessage.profilePictureClicked() 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: root.profilePictureClicked(this, mouse)
}
} }
} }
Column {
ColumnLayout {
spacing: 4 spacing: 4
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.topMargin: 10
Layout.fillWidth: true Layout.fillWidth: true
StatusPinMessageDetails {
visible: messageDetails.isPinned && !editMode Loader {
pinnedMsgInfoText: statusMessage.pinnedMsgInfoText active: root.isPinned && !editMode
pinnedBy: messageDetails.pinnedBy visible: active
sourceComponent: StatusPinMessageDetails {
pinnedMsgInfoText: root.pinnedMsgInfoText
pinnedBy: root.pinnedBy
}
} }
StatusMessageHeader { StatusMessageHeader {
id: messageHeader Layout.fillWidth: true
width: parent.width sender: root.messageDetails.sender
displayName: messageDetails.displayName amISender: root.messageDetails.amISender
secondaryName: messageDetails.secondaryName resendText: root.resendText
tertiaryDetail: messageDetails.chatID showResendButton: root.hasExpired && root.messageDetails.amISender
isContact: messageDetails.isContact onClicked: root.senderNameClicked(sender, mouse)
trustIndicator: messageDetails.trustIndicator onResendClicked: root.resendClicked()
resendText: statusMessage.resendText visible: root.showHeader && !editMode
showResendButton: messageDetails.hasExpired && messageDetails.amISender timestamp.text: root.timestampString
onClicked: statusMessage.senderNameClicked() timestamp.tooltip.text: root.timestampTooltipString
onResendClicked: statusMessage.resendClicked()
visible: !editMode
} }
Loader { Loader {
active: !editMode && !!messageDetails.messageText Layout.fillWidth: true
width: parent.width active: !editMode && !!root.messageDetails.messageText
visible: active visible: active
sourceComponent: StatusTextMessage { sourceComponent: StatusTextMessage {
width: parent.width textField.text: {
textField.text: messageDetails.messageText 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 { Loader {
active: messageDetails.contentType === StatusMessage.ContentType.Image && !editMode active: root.messageDetails.contentType === StatusMessage.ContentType.Image && !editMode
visible: active visible: active
sourceComponent: StatusImageMessage { sourceComponent: StatusImageMessage {
source: messageDetails.contentType === StatusMessage.ContentType.Image ? messageDetails.messageContent : "" source: root.messageDetails.contentType === StatusMessage.ContentType.Image ? root.messageDetails.messageContent : ""
onClicked: statusMessage.imageClicked() onClicked: root.imageClicked(image, mouse, imageSource)
shapeType: messageDetails.amISender ? StatusImageMessage.ShapeType.RIGHT_ROUNDED : StatusImageMessage.ShapeType.LEFT_ROUNDED shapeType: root.messageDetails.amISender ? StatusImageMessage.ShapeType.RIGHT_ROUNDED : StatusImageMessage.ShapeType.LEFT_ROUNDED
} }
} }
StatusSticker { Loader {
visible: messageDetails.contentType === StatusMessage.ContentType.Sticker && !editMode active: root.messageDetails.contentType === StatusMessage.ContentType.Sticker && !editMode
image.source: messageDetails.messageContent visible: active
onLoaded: statusMessage.stickerLoaded() sourceComponent: StatusSticker {
image.source: root.messageDetails.messageContent
onLoaded: root.stickerLoaded()
onClicked: {
root.stickerClicked()
}
}
} }
Loader { Loader {
active: messageDetails.contentType === StatusMessage.ContentType.Audio && !editMode active: root.messageDetails.contentType === StatusMessage.ContentType.Audio && !editMode
visible: active visible: active
sourceComponent: StatusAudioMessage { sourceComponent: StatusAudioMessage {
audioSource: messageDetails.messageContent audioSource: root.messageDetails.messageContent
hovered: hoverHandler.hovered hovered: hoverHandler.hovered
audioMessageInfoText: statusMessage.audioMessageInfoText audioMessageInfoText: root.audioMessageInfoText
} }
} }
Loader { Loader {
id: linksLoader id: linksLoader
active: !!linksLoader.sourceComponent active: !root.editMode
visible: active visible: active
} }
Loader { Loader {
id: transactionBubbleLoader id: transactionBubbleLoader
active: messageDetails.contentType === StatusMessage.ContentType.Transaction && !editMode active: root.messageDetails.contentType === StatusMessage.ContentType.Transaction && !editMode
visible: active visible: active
} }
Loader { Loader {
id: invitationBubbleLoader id: invitationBubbleLoader
active: messageDetails.contentType === StatusMessage.ContentType.Invitation && !editMode active: root.messageDetails.contentType === StatusMessage.ContentType.Invitation && !editMode
visible: active visible: active
} }
StatusEditMessage { StatusEditMessage {
id: editComponent id: editComponent
width: parent.width Layout.fillWidth: true
msgText: messageDetails.messageText Layout.rightMargin: 16
visible: editMode active: root.editMode
saveButtonText: statusMessage.saveButtonText visible: active
cancelButtonText: statusMessage.cancelButtonText msgText: root.messageDetails.messageText
onCancelEditClicked: editMode = false saveButtonText: root.saveButtonText
onEditCompleted: { cancelButtonText: root.cancelButtonText
editMode = false onEditCancelled: root.editCancelled()
statusMessage.editCompleted(newMsgText) onEditCompleted: root.editCompleted(newMsgText)
}
} }
StatusBaseText { StatusBaseText {
id: retryLbl
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
text: statusMessage.resendText text: root.resendText
font.pixelSize: 12 font.pixelSize: 12
visible: messageDetails.hasExpired && messageDetails.amISender && !messageDetails.timestamp && !editMode visible: root.hasExpired && root.messageDetails.amISender && !root.timestamp && !editMode
MouseArea { MouseArea {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
anchors.fill: parent anchors.fill: parent
onClicked: statusMessage.resendClicked() onClicked: root.resendClicked()
} }
} }
Loader { Loader {
id: footer active: root.reactionsModel.count > 0
active: sourceComponent && !editMode
visible: active 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.rightMargin: 20
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: -8 anchors.topMargin: -8
visible: hoverHandler.hovered && !editMode visible: hoverHandler.hovered && !root.hideQuickActions
} }
} }

View File

@ -6,24 +6,13 @@ QtObject {
id: msgDetails id: msgDetails
property bool amISender: false property bool amISender: false
property string displayName: ""
property string secondaryName: "" property StatusMessageSenderDetails sender: StatusMessageSenderDetails { }
property string chatID: ""
property StatusImageSettings profileImage: StatusImageSettings {
width: 40
height: 40
}
property bool isEdited: false property bool isEdited: false
property string messageText: ""
property int contentType: 0 property int contentType: 0
property string messageText: ""
property string messageContent: "" 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: ""
} }

View File

@ -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
}

View File

@ -28,6 +28,10 @@ Loader {
distinctiveColors: Theme.palette.identiconRingColors 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 : sourceComponent: statusSmartIdenticon.icon.isLetterIdenticon ? letterIdenticon :
!!statusSmartIdenticon.image.source.toString() ? roundedImage : !!statusSmartIdenticon.image.source.toString() ? roundedImage :
!!statusSmartIdenticon.icon.name.toString() ? roundedIcon : letterIdenticon !!statusSmartIdenticon.icon.name.toString() ? roundedIcon : letterIdenticon

View File

@ -6,45 +6,63 @@ import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
Item { Item {
id: editText id: root
property alias inputComponent: chatInputLoader.sourceComponent property alias inputComponent: chatInputLoader.sourceComponent
property alias active: chatInputLoader.active
property string cancelButtonText: "" property string cancelButtonText: ""
property string saveButtonText: "" property string saveButtonText: ""
property string msgText: "" property string msgText: ""
signal cancelEditClicked() signal editCancelled()
signal editCompleted(var newMsgText) signal editCompleted(var newMsgText)
height: childrenRect.height implicitHeight: layout.implicitHeight
implicitWidth: layout.implicitWidth
ColumnLayout { ColumnLayout {
id: layout
anchors.fill: parent
spacing: 4 spacing: 4
Loader { Loader {
id: chatInputLoader 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 { sourceComponent: StatusInput {
width: editText.width readonly property string messageText: input.text
placeholderText: "" width: parent.width
input.placeholderText: ""
input.text: msgText input.text: msgText
maximumHeight: 40 maximumHeight: 40
} }
} }
RowLayout { RowLayout {
spacing: 4 spacing: 4
StatusFlatButton { StatusFlatButton {
id: cancelBtn id: cancelBtn
text: cancelButtonText text: cancelButtonText
size: StatusBaseButton.Size.Small size: StatusBaseButton.Size.Small
onClicked: cancelEditClicked() onClicked: {
editCancelled()
}
} }
StatusButton { StatusButton {
id: saveBtn id: saveBtn
text: saveButtonText text: saveButtonText
size: StatusBaseButton.Size.Small size: StatusBaseButton.Size.Small
enabled: chatInputLoader.item.input.text.trim().length > 0 enabled: !!chatInputLoader.item && chatInputLoader.item.messageText.trim().length > 0
onClicked: editCompleted(chatInputLoader.item.input.text) onClicked: {
editCompleted(!chatInputLoader.item ? "" : chatInputLoader.item.messageText)
}
} }
} }
} }

View File

@ -25,10 +25,10 @@ Item {
property string loadingImageText: "" property string loadingImageText: ""
property string errorLoadingImageText: "" property string errorLoadingImageText: ""
signal clicked(var image, var mouse) signal clicked(var image, var mouse, var imageSource)
width: loadingImage.visible ? loadingImage.width : imageMessage.width implicitWidth: loadingImage.visible ? loadingImage.width : imageMessage.width
height: loadingImage.visible ? loadingImage.height : imageMessage.paintedHeight implicitHeight: loadingImage.visible ? loadingImage.height : imageMessage.paintedHeight
QtObject { QtObject {
id: _internal id: _internal
@ -87,7 +87,7 @@ Item {
_internal.pausePlaying = ! _internal.pausePlaying _internal.pausePlaying = ! _internal.pausePlaying
return return
} }
imageContainer.clicked(imageMessage, mouse) imageContainer.clicked(imageMessage, mouse, imageMessage.source)
} }
} }
} }

View File

@ -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")
}
}
}
}

View File

@ -3,36 +3,39 @@ import QtQuick.Layouts 1.14
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
Item { Item {
id: statusMessageHeader id: root
property StatusMessageSenderDetails sender: StatusMessageSenderDetails { }
property alias displayNameLabel: primaryDisplayName property alias displayNameLabel: primaryDisplayName
property alias secondaryNameLabel: secondaryDisplayName property alias secondaryNameLabel: secondaryDisplayName
property alias tertiaryDetailsLabel: tertiaryDetailText property alias tertiaryDetailsLabel: tertiaryDetailText
property alias timestamp: timestampText property alias timestamp: timestampText
property string displayName: "" property string tertiaryDetail: sender.id
property string secondaryName: ""
property string tertiaryDetail: ""
property string resendText: "" property string resendText: ""
property bool showResendButton: false property bool showResendButton: false
property bool isContact: false property bool isContact: sender.isContact
property var trustIndicator: StatusContactVerificationIcons.TrustedType.None property int trustIndicator: sender.trustIndicator
property bool amISender: false
signal clicked() signal clicked(var sender, var mouse)
signal resendClicked() signal resendClicked()
height: childrenRect.height implicitHeight: layout.implicitHeight
width: primaryDisplayName.width + (secondaryDisplayName.visible ? secondaryDisplayName.width + header.spacing : 0) implicitWidth: layout.implicitWidth
RowLayout { RowLayout {
id: header id: layout
spacing: 4 spacing: 4
TextEdit { TextEdit {
id: primaryDisplayName id: primaryDisplayName
Layout.alignment: Qt.AlignBottom
font.family: Theme.palette.baseFont.name font.family: Theme.palette.baseFont.name
font.weight: Font.Medium font.weight: Font.Medium
font.pixelSize: 15 font.pixelSize: 15
@ -41,7 +44,7 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
selectByMouse: true selectByMouse: true
color: Theme.palette.primaryColor1 color: Theme.palette.primaryColor1
text: displayName text: root.amISender ? qsTr("You") : root.sender.displayName
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
@ -49,47 +52,45 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true hoverEnabled: true
onClicked: { onClicked: {
statusMessageHeader.clicked() root.clicked(this, mouse)
} }
} }
Layout.alignment: Qt.AlignBottom
} }
StatusContactVerificationIcons { StatusContactVerificationIcons {
isContact: statusMessageHeader.isContact visible: !root.amISender
trustIndicator: statusMessageHeader.trustIndicator isContact: root.isContact
trustIndicator: root.trustIndicator
} }
StatusBaseText { StatusBaseText {
id: secondaryDisplayName id: secondaryDisplayName
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: !root.amISender && !!root.sender.secondaryName
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
font.pixelSize: 10 font.pixelSize: 10
text: secondaryName text: `(${root.sender.secondaryName})`
visible: !!text
} }
StatusBaseText { StatusBaseText {
id: dotSeparator1 Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: true visible: secondaryDisplayName.visible
font.pixelSize: 10 font.pixelSize: 10
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
text: "." text: "•"
visible: secondaryDisplayName.visible
} }
StatusBaseText { StatusBaseText {
id: tertiaryDetailText id: tertiaryDetailText
visible: !root.amISender
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.maximumWidth: 58
font.pixelSize: 10 font.pixelSize: 10
elide: Text.ElideMiddle elide: Text.ElideMiddle
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
text: tertiaryDetail text: Utils.elideText(tertiaryDetail, 5, 3)
} }
StatusBaseText { StatusBaseText {
id: dotSeparator2 Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: true visible: tertiaryDetailText.visible
font.pixelSize: 10 font.pixelSize: 10
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
text: "." text: "•"
visible: tertiaryDetailText.visible
} }
StatusTimeStampLabel { StatusTimeStampLabel {
id: timestampText id: timestampText
@ -98,12 +99,12 @@ Item {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
font.pixelSize: 12 font.pixelSize: 12
text: statusMessageHeader.resendText text: root.resendText
visible: showResendButton && !!timestampText.text visible: showResendButton && !!timestampText.text
MouseArea { MouseArea {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
anchors.fill: parent anchors.fill: parent
onClicked: statusMessageHeader.resendClicked() onClicked: root.resendClicked()
} }
} }
} }

View File

@ -6,29 +6,29 @@ import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
Rectangle { Rectangle {
id: buttonsContainer id: root
property list<Item> quickActions property list<Item> items
QtObject { QtObject {
id: _internal id: _internal
readonly property int containerMargin: 2 readonly property int containerMargin: 2
} }
width: buttonRow.width + _internal.containerMargin * 2 implicitWidth: buttonRow.width + _internal.containerMargin * 2
height: 36 implicitHeight: 36
radius: 8 radius: 8
color: Theme.palette.statusSelect.menuItemBackgroundColor color: Theme.palette.statusSelect.menuItemBackgroundColor
layer.enabled: true layer.enabled: true
layer.effect: DropShadow { layer.effect: DropShadow {
width: buttonsContainer.width width: root.width
height: buttonsContainer.height height: root.height
x: buttonsContainer.x x: root.x
y: buttonsContainer.y + 10 y: root.y + 10
horizontalOffset: 0 horizontalOffset: 0
verticalOffset: 2 verticalOffset: 2
source: buttonsContainer source: root
radius: 10 radius: 10
samples: 15 samples: 15
color: Theme.palette.dropShadow color: Theme.palette.dropShadow
@ -39,13 +39,13 @@ Rectangle {
spacing: _internal.containerMargin spacing: _internal.containerMargin
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: _internal.containerMargin anchors.leftMargin: _internal.containerMargin
anchors.verticalCenter: buttonsContainer.verticalCenter anchors.verticalCenter: root.verticalCenter
height: parent.height - 2 * _internal.containerMargin height: parent.height - 2 * _internal.containerMargin
} }
onQuickActionsChanged: { onItemsChanged: {
for (let idx in quickActions) { for (let idx in items) {
quickActions[idx].parent = buttonRow items[idx].parent = buttonRow
} }
} }
} }

View File

@ -7,18 +7,19 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
Loader { Item {
id: chatReply id: root
property StatusMessageDetails replyDetails property StatusMessageDetails replyDetails
property string audioMessageInfoText: "" property string audioMessageInfoText: ""
signal replyProfileClicked() signal replyProfileClicked(var sender, var mouse)
active: visible implicitHeight: layout.implicitHeight
implicitWidth: layout.implicitWidth
sourceComponent: RowLayout { RowLayout {
id: replyLayout id: layout
spacing: 8 spacing: 8
Shape { Shape {
id: replyCorner id: replyCorner
@ -56,13 +57,16 @@ Loader {
StatusSmartIdenticon { StatusSmartIdenticon {
id: profileImage id: profileImage
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
image: replyDetails.profileImage name: replyDetails.sender.userName
name: replyDetails.displayName image: replyDetails.sender.profileImage.imageSettings
icon: replyDetails.sender.profileImage.iconSettings
ringSettings: replyDetails.sender.profileImage.ringSettings
MouseArea { MouseArea {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent anchors.fill: parent
onClicked: replyProfileClicked() onClicked: replyProfileClicked(this, mouse)
} }
} }
TextEdit { TextEdit {
@ -74,7 +78,7 @@ Loader {
font.weight: Font.Medium font.weight: Font.Medium
selectByMouse: true selectByMouse: true
readOnly: true readOnly: true
text: replyDetails.displayName text: replyDetails.amISender ? qsTr("You") : replyDetails.sender.displayName
} }
} }
StatusTextMessage { StatusTextMessage {
@ -85,13 +89,14 @@ Loader {
textField.height: 18 textField.height: 18
clip: true clip: true
visible: !!replyDetails.messageText visible: !!replyDetails.messageText
allowShowMore: false
} }
StatusImageMessage { StatusImageMessage {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: imageAlias.paintedHeight Layout.preferredHeight: imageAlias.paintedHeight
imageWidth: 56 imageWidth: 56
source: replyDetails.contentType === StatusMessage.ContentType.Image ? replyDetails.messageContent : "" source: replyDetails.contentType === StatusMessage.ContentType.Image ? replyDetails.messageContent : ""
visible: replyDetails.contentType === StatusMessage.ContentType.Image // visible: replyDetails.contentType === StatusMessage.ContentType.Image
shapeType: StatusImageMessage.ShapeType.ROUNDED shapeType: StatusImageMessage.ShapeType.ROUNDED
} }
Item { Item {
@ -116,7 +121,7 @@ Loader {
height: 22 height: 22
isPreview: true isPreview: true
audioSource: replyDetails.messageContent audioSource: replyDetails.messageContent
audioMessageInfoText: chatReply.audioMessageInfoText audioMessageInfoText: root.audioMessageInfoText
} }
} }
} }

View File

@ -1,4 +1,5 @@
import QtQuick 2.13 import QtQuick 2.13
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.13 import QtGraphicalEffects 1.13
@ -11,14 +12,34 @@ Loader {
active: visible active: visible
sourceComponent: Rectangle { sourceComponent: Control {
height: 24 verticalPadding: 3
width: layout.width + 16 leftPadding: 2
color: Theme.palette.pinColor2 rightPadding: 6
radius: 12
RowLayout { background: Rectangle {
id: layout readonly property color translucentColor: Theme.palette.pinColor2
anchors.centerIn: parent
implicitWidth: 24
implicitHeight: 24
color: Qt.rgba(translucentColor.r,
translucentColor.g,
translucentColor.b, 1)
opacity: translucentColor.a
layer.enabled: true
radius: 12
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 { StatusIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: 16 Layout.preferredWidth: 16

View File

@ -6,24 +6,33 @@ import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
Item { Item {
id: textMessage id: root
property int contentType: 0
property alias textField: chatText 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 { QtObject {
id: _internal id: d
property bool readMore: false 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 { TextEdit {
id: chatText 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 selectedTextColor: Theme.palette.directColor1
selectionColor: Theme.palette.primaryColor3 selectionColor: Theme.palette.primaryColor3
color: Theme.palette.directColor1 color: Theme.palette.directColor1
@ -33,12 +42,12 @@ Item {
wrapMode: Text.Wrap wrapMode: Text.Wrap
readOnly: true readOnly: true
selectByMouse: true selectByMouse: true
height: _internal.veryLongChatText && !_internal.readMore ? Math.min(implicitHeight, 200) : implicitHeight onLinkActivated: {
width: parent.width root.linkActivated(link);
clip: height < implicitHeight }
onLinkActivated: textMessage.linkActivated(link)
onLinkHovered: { onLinkHovered: {
cursorShape: Qt.PointingHandCursor // Strange thing. Without this empty stub the cursorShape
// is not changed to pointingHandCursor.
} }
} }
@ -60,7 +69,7 @@ Item {
Loader { Loader {
id: opMask id: opMask
active: showMoreLoader.active && !_internal.readMore active: showMoreLoader.active && !d.readMore
anchors.fill: chatText anchors.fill: chatText
sourceComponent: OpacityMask { sourceComponent: OpacityMask {
source: chatText source: chatText
@ -70,17 +79,17 @@ Item {
Loader { Loader {
id: showMoreLoader id: showMoreLoader
active: _internal.veryLongChatText active: root.allowShowMore && d.veryLongChatText
anchors.top: chatText.bottom visible: active
anchors.topMargin: -10 anchors.verticalCenter: chatText.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: StatusRoundButton { sourceComponent: StatusRoundButton {
implicitWidth: 24 implicitWidth: 24
implicitHeight: 24 implicitHeight: 24
type: StatusRoundButton.Type.Secondary type: StatusRoundButton.Type.Secondary
icon.name: _internal.readMore ? "chevron-up": "chevron-down" icon.name: d.readMore ? "chevron-up": "chevron-down"
onClicked: { onClicked: {
_internal.readMore = !_internal.readMore d.readMore = !d.readMore
} }
} }
} }

View File

@ -13,6 +13,7 @@ StatusChatToolBar 0.1 StatusChatToolBar.qml
StatusContactRequestsIndicatorListItem 0.1 StatusContactRequestsIndicatorListItem.qml StatusContactRequestsIndicatorListItem 0.1 StatusContactRequestsIndicatorListItem.qml
StatusEmoji 0.1 StatusEmoji.qml StatusEmoji 0.1 StatusEmoji.qml
StatusContactVerificationIcons 0.1 StatusContactVerificationIcons.qml StatusContactVerificationIcons 0.1 StatusContactVerificationIcons.qml
StatusDateGroupLabel 0.1 StatusDateGroupLabel.qml
StatusDescriptionListItem 0.1 StatusDescriptionListItem.qml StatusDescriptionListItem 0.1 StatusDescriptionListItem.qml
StatusLetterIdenticon 0.1 StatusLetterIdenticon.qml StatusLetterIdenticon 0.1 StatusLetterIdenticon.qml
StatusListItem 0.1 StatusListItem.qml StatusListItem 0.1 StatusListItem.qml
@ -31,6 +32,7 @@ StatusExpandableItem 0.1 StatusExpandableItem.qml
StatusSmartIdenticon 0.1 StatusSmartIdenticon.qml StatusSmartIdenticon 0.1 StatusSmartIdenticon.qml
StatusMessage 0.1 StatusMessage.qml StatusMessage 0.1 StatusMessage.qml
StatusMessageDetails 0.1 StatusMessageDetails.qml StatusMessageDetails 0.1 StatusMessageDetails.qml
StatusMessageSenderDetails 0.1 StatusMessageSenderDetails.qml
StatusTagSelector 0.1 StatusTagSelector.qml StatusTagSelector 0.1 StatusTagSelector.qml
StatusToastMessage 0.1 StatusToastMessage.qml StatusToastMessage 0.1 StatusToastMessage.qml
StatusWizardStepper 0.1 StatusWizardStepper.qml StatusWizardStepper 0.1 StatusWizardStepper.qml

View File

@ -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
}
}

View File

@ -154,5 +154,12 @@ ThemePalette {
property color menuItemBackgroundColor: baseColor2 property color menuItemBackgroundColor: baseColor2
property color menuItemHoverBackgroundColor: directColor7 property color menuItemHoverBackgroundColor: directColor7
} }
property QtObject statusMessage: QtObject {
property color emojiReactionBackground: "#2d2823"
property color emojiReactionBackgroundHovered: "#3a3632"
property color emojiReactionActiveBackground: "#353a4d"
property color emojiReactionActiveBackgroundHovered: "#cbd5f1"
}
} }

View File

@ -152,5 +152,12 @@ ThemePalette {
property color menuItemBackgroundColor: white property color menuItemBackgroundColor: white
property color menuItemHoverBackgroundColor: baseColor2 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"
}
} }

View File

@ -243,7 +243,8 @@ QtObject {
} }
function getColor(name, alpha) { function getColor(name, alpha) {
return !!alpha ? alphaColor(StatusColors.colors[name], alpha) : StatusColors.colors[name] return !!alpha ? alphaColor(StatusColors.colors[name], alpha)
: StatusColors.colors[name]
} }
} }

View File

@ -1,6 +1,8 @@
pragma Singleton pragma Singleton
import QtQuick 2.13 import QtQuick 2.13
import StatusQ.Core.Theme 0.1
import "./xss.js" as XSS
QtObject { 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) { function delegateModelSort(srcGroup, dstGroup, lessThan) {
const insertPosition = (lessThan, item) => { const insertPosition = (lessThan, item) => {
let lower = 0 let lower = 0
@ -173,6 +240,10 @@ QtObject {
dstGroup.move(item.itemsIndex, index) dstGroup.move(item.itemsIndex, index)
} }
} }
function elideText(text, leftCharsCount, rightCharsCount = leftCharsCount) {
return text.substr(0, leftCharsCount) + "..." + text.substr(text.length - rightCharsCount)
}
} }

View File

@ -1,4 +1,6 @@
module StatusQ.Core.Utils module StatusQ.Core.Utils
EmojiJSON 1.0 emojiList.js
XSS 1.0 xss.js
singleton Utils 0.1 Utils.qml singleton Utils 0.1 Utils.qml
singleton Emoji 0.1 Emoji.qml singleton Emoji 0.1 Emoji.qml

File diff suppressed because it is too large Load Diff

View File

@ -14,3 +14,4 @@ StatusAnimatedStack 0.1 StatusAnimatedStack.qml
StatusScrollView 0.1 StatusScrollView.qml StatusScrollView 0.1 StatusScrollView.qml
StatusListView 0.1 StatusListView.qml StatusListView 0.1 StatusListView.qml
StatusGridView 0.1 StatusGridView.qml StatusGridView 0.1 StatusGridView.qml
StatusProfileImageSettings 0.1 StatusProfileImageSettings.qml