feat(StatusMessage): Introducing a new StatusQ Component for Chat Messages
The below mentioned features have been implemented in this component 1. Profile Picture of sender 2. Sender details - name, secondaryName, chatID 3. Text content 4. Image Content 5. Sticker Content 6. Audio Content 7. Reply Component with all the details for the message beinf replied to 8. Pinned component for Pinned message 9. Edit Component to edit the message 10. Loades to load the below - a. Link content loader b. transaction content c. invitation bubble d. footer - in this case to show emoji reactions to a message e. quick actions loader to show the related quick actions
This commit is contained in:
parent
e7b9462c84
commit
bd41957c80
|
@ -136,6 +136,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
appView: Loader {
|
appView: Loader {
|
||||||
|
|
|
@ -4,11 +4,12 @@ import StatusQ.Controls 0.1
|
||||||
import StatusQ.Popups 0.1
|
import StatusQ.Popups 0.1
|
||||||
import StatusQ.Components 0.1
|
import StatusQ.Components 0.1
|
||||||
import StatusQ.Layout 0.1
|
import StatusQ.Layout 0.1
|
||||||
|
import StatusQ.Core 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
import "data" 1.0
|
import "data" 1.0
|
||||||
|
|
||||||
StatusAppTwoPanelLayout {
|
StatusAppThreePanelLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
leftPanel: Item {
|
leftPanel: Item {
|
||||||
|
@ -250,4 +251,98 @@ StatusAppTwoPanelLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
centerPanel: ListView {
|
||||||
|
id: messageList
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 15
|
||||||
|
clip: true
|
||||||
|
model: Models.chatMessagesModel
|
||||||
|
delegate: StatusMessage {
|
||||||
|
id: delegate
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
audioMessageInfoText: "Audio Message"
|
||||||
|
cancelButtonText: "Cancel"
|
||||||
|
saveButtonText: "Save"
|
||||||
|
loadingImageText: "Loading image..."
|
||||||
|
errorLoadingImageText: "Error loading the image"
|
||||||
|
resendText: "Resend"
|
||||||
|
pinnedMsgInfoText: "Pinned by"
|
||||||
|
|
||||||
|
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 {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
source: model.profileImage
|
||||||
|
isIdenticon: model.isIdenticon
|
||||||
|
}
|
||||||
|
messageText: model.message
|
||||||
|
hasMention: model.hasMention
|
||||||
|
contactType: model.contactType
|
||||||
|
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 {
|
||||||
|
width: 20
|
||||||
|
height: 20
|
||||||
|
source: model.isReply ? model.replyProfileImage: ""
|
||||||
|
isIdenticon: model.isReply ? model.replyIsIdenticon: ""
|
||||||
|
}
|
||||||
|
messageText: model.isReply ? model.replyMessageText: ""
|
||||||
|
contentType: model.replyContentType
|
||||||
|
messageContent: model.replyMessageContent
|
||||||
|
}
|
||||||
|
quickActions: [
|
||||||
|
StatusFlatRoundButton {
|
||||||
|
id: emojiBtn
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
icon.name: "reaction-b"
|
||||||
|
type: StatusFlatRoundButton.Type.Tertiary
|
||||||
|
tooltip.text: "Add reaction"
|
||||||
|
},
|
||||||
|
StatusFlatRoundButton {
|
||||||
|
id: replyBtn
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
icon.name: "reply"
|
||||||
|
type: StatusFlatRoundButton.Type.Tertiary
|
||||||
|
tooltip.text: "Reply"
|
||||||
|
},
|
||||||
|
StatusFlatRoundButton {
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
icon.name: "tiny/edit"
|
||||||
|
type: StatusFlatRoundButton.Type.Tertiary
|
||||||
|
tooltip.text: "Edit"
|
||||||
|
onClicked: {
|
||||||
|
delegate.editMode = !delegate.editMode
|
||||||
|
}
|
||||||
|
},
|
||||||
|
StatusFlatRoundButton {
|
||||||
|
id: otherBtn
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
icon.name: "more"
|
||||||
|
type: StatusFlatRoundButton.Type.Tertiary
|
||||||
|
tooltip.text: "More"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,211 @@
|
||||||
|
import QtQuick 2.14
|
||||||
|
import QtQuick.Layouts 1.14
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
|
||||||
|
import "./private/statusMessage"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: statusMessage
|
||||||
|
|
||||||
|
enum ContentType {
|
||||||
|
Unknown = 0,
|
||||||
|
Text = 1,
|
||||||
|
Emoji = 2,
|
||||||
|
Image = 3,
|
||||||
|
Sticker = 4,
|
||||||
|
Audio = 5,
|
||||||
|
Transaction = 6,
|
||||||
|
Invitation = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ContactType {
|
||||||
|
STANDARD = 0,
|
||||||
|
RENAME = 1,
|
||||||
|
CONTACT = 2,
|
||||||
|
VERIFIED = 3,
|
||||||
|
UNTRUSTWORTHY = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
property alias messageHeader: messageHeader
|
||||||
|
property alias quickActions:quickActionsPanel.quickActions
|
||||||
|
property alias statusChatInput: editComponent.inputComponent
|
||||||
|
property alias linksComponent: linksLoader.sourceComponent
|
||||||
|
property alias footerComponent: footer.sourceComponent
|
||||||
|
property alias timestamp: messageHeader.timestamp
|
||||||
|
|
||||||
|
property string resendText: ""
|
||||||
|
property string cancelButtonText: ""
|
||||||
|
property string saveButtonText: ""
|
||||||
|
property string loadingImageText: ""
|
||||||
|
property string errorLoadingImageText: ""
|
||||||
|
property string audioMessageInfoText: ""
|
||||||
|
property string pinnedMsgInfoText: ""
|
||||||
|
|
||||||
|
property bool isAppWindowActive: false
|
||||||
|
property bool editMode: false
|
||||||
|
property bool isAReply: false
|
||||||
|
property StatusMessageDetails messageDetails: StatusMessageDetails {}
|
||||||
|
property StatusMessageDetails replyDetails: StatusMessageDetails {}
|
||||||
|
|
||||||
|
signal profilePictureClicked()
|
||||||
|
signal senderNameClicked()
|
||||||
|
signal editCompleted(var newMsgText)
|
||||||
|
signal replyProfileClicked()
|
||||||
|
signal stickerLoaded()
|
||||||
|
signal imageClicked(var imageSource)
|
||||||
|
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"
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: hoverHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: messageLayout
|
||||||
|
width: parent.width
|
||||||
|
StatusMessageReply {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: isAReply
|
||||||
|
replyDetails: statusMessage.replyDetails
|
||||||
|
onReplyProfileClicked: statusMessage.replyProfileClicked()
|
||||||
|
audioMessageInfoText: statusMessage.audioMessageInfoText
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
spacing: 8
|
||||||
|
Layout.fillWidth: true
|
||||||
|
StatusSmartIdenticon {
|
||||||
|
id: profileImage
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.topMargin: 10
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
image: messageDetails.profileImage
|
||||||
|
name: messageHeader.displayName
|
||||||
|
MouseArea {
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: statusMessage.profilePictureClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column {
|
||||||
|
spacing: 4
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.topMargin: 10
|
||||||
|
Layout.fillWidth: true
|
||||||
|
StatusPinMessageDetails {
|
||||||
|
visible: messageDetails.isPinned && !editMode
|
||||||
|
pinnedMsgInfoText: statusMessage.pinnedMsgInfoText
|
||||||
|
pinnedBy: messageDetails.pinnedBy
|
||||||
|
}
|
||||||
|
StatusMessageHeader {
|
||||||
|
id: messageHeader
|
||||||
|
width: parent.width
|
||||||
|
displayName: messageDetails.displayName
|
||||||
|
secondaryName: messageDetails.secondaryName
|
||||||
|
tertiaryDetail: messageDetails.chatID
|
||||||
|
icon1.name: messageDetails.contactType === StatusMessage.ContactType.CONTACT ? "tiny/tiny-contact" : ""
|
||||||
|
icon2.name: messageDetails.contactType === StatusMessage.ContactType.VERIFIED ? "tiny/tiny-checkmark" :
|
||||||
|
messageDetails.contactType === StatusMessage.ContactType.UNTRUSTWORTHY ? "tiny/subtract": ""
|
||||||
|
icon2.background.color: messageDetails.contactType === StatusMessage.ContactType.UNTRUSTWORTHY ? Theme.palette.dangerColor1 : Theme.palette.primaryColor1
|
||||||
|
icon2.color: Theme.palette.indirectColor1
|
||||||
|
resendText: statusMessage.resendText
|
||||||
|
showResendButton: messageDetails.hasExpired && messageDetails.amISender
|
||||||
|
onClicked: statusMessage.senderNameClicked()
|
||||||
|
onResendClicked: statusMessage.resendClicked()
|
||||||
|
visible: !editMode
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
active: !editMode && !!messageDetails.messageText
|
||||||
|
width: parent.width
|
||||||
|
visible: active
|
||||||
|
sourceComponent: StatusTextMessage {
|
||||||
|
width: parent.width
|
||||||
|
textField.text: messageDetails.messageText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
active: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StatusSticker {
|
||||||
|
visible: messageDetails.contentType === StatusMessage.ContentType.Sticker && !editMode
|
||||||
|
image.source: messageDetails.messageContent
|
||||||
|
onLoaded: statusMessage.stickerLoaded()
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
active: messageDetails.contentType === StatusMessage.ContentType.Audio && !editMode
|
||||||
|
visible: active
|
||||||
|
sourceComponent: StatusAudioMessage {
|
||||||
|
audioSource: messageDetails.messageContent
|
||||||
|
hovered: hoverHandler.hovered
|
||||||
|
audioMessageInfoText: statusMessage.audioMessageInfoText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
id: linksLoader
|
||||||
|
active: !!linksLoader.sourceComponent
|
||||||
|
visible: active
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
id: transactionBubbleLoader
|
||||||
|
active: messageDetails.contentType === StatusMessage.ContentType.Transaction && !editMode
|
||||||
|
visible: active
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
id: invitationBubbleLoader
|
||||||
|
active: 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
id: retryLbl
|
||||||
|
color: Theme.palette.dangerColor1
|
||||||
|
text: statusMessage.resendText
|
||||||
|
font.pixelSize: 12
|
||||||
|
visible: messageDetails.hasExpired && messageDetails.amISender && !messageDetails.timestamp && !editMode
|
||||||
|
MouseArea {
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: statusMessage.resendClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
id: footer
|
||||||
|
active: sourceComponent && !editMode
|
||||||
|
visible: active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusMessageQuickActions {
|
||||||
|
id: quickActionsPanel
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 20
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: -8
|
||||||
|
visible: hoverHandler.hovered && !editMode
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
|
||||||
|
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 bool isEdited: false
|
||||||
|
property string messageText: ""
|
||||||
|
property int contentType: 0
|
||||||
|
property string messageContent: ""
|
||||||
|
property int contactType: 0
|
||||||
|
property bool hasMention: false
|
||||||
|
property bool isPinned: false
|
||||||
|
property string pinnedBy: ""
|
||||||
|
property bool hasExpired: false
|
||||||
|
property string timestamp: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtMultimedia 5.14
|
||||||
|
import QtQuick.Layouts 1.14
|
||||||
|
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
// To-do update as per latest design -> Audio graphs. Also the player should ideally be in the BE?
|
||||||
|
Rectangle {
|
||||||
|
id: audioChatMessage
|
||||||
|
|
||||||
|
property string audioMessageInfoText: ""
|
||||||
|
property bool isPreview: false
|
||||||
|
property bool hovered: false
|
||||||
|
property string audioSource: ""
|
||||||
|
|
||||||
|
width: 320
|
||||||
|
height: 32
|
||||||
|
radius: 20
|
||||||
|
|
||||||
|
color: hovered ? Theme.palette.directColor8 : Theme.palette.baseColor2
|
||||||
|
|
||||||
|
Audio {
|
||||||
|
id: audioMessage
|
||||||
|
source: audioSource
|
||||||
|
notifyInterval: 150
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: preview
|
||||||
|
visible: isPreview
|
||||||
|
spacing: 5
|
||||||
|
anchors.centerIn: parent
|
||||||
|
StatusIcon {
|
||||||
|
id: icon
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.preferredWidth: 14
|
||||||
|
Layout.preferredHeight: 14
|
||||||
|
icon: "audio"
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
text: audioMessageInfoText
|
||||||
|
font.pixelSize: 13
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusFlatRoundButton {
|
||||||
|
id: playButton
|
||||||
|
width: 15
|
||||||
|
height: 15
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: !isPreview
|
||||||
|
|
||||||
|
type: StatusFlatRoundButton.Type.Tertiary
|
||||||
|
color: "transparent"
|
||||||
|
icon.name: audioMessage.playbackState == Audio.PlayingState ? "pause-filled" : "play-filled"
|
||||||
|
icon.color: Theme.palette.directColor1
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if(audioMessage.playbackState === Audio.PlayingState){
|
||||||
|
audioMessage.pause();
|
||||||
|
} else {
|
||||||
|
audioMessage.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: 2
|
||||||
|
width: 240
|
||||||
|
color: Theme.palette.directColor5
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: playButton.right
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
visible: !isPreview
|
||||||
|
Rectangle {
|
||||||
|
id: progress
|
||||||
|
height: 2
|
||||||
|
width: {
|
||||||
|
if(audioMessage.duration === 0) return 0;
|
||||||
|
if(audioMessage.playbackState === Audio.StoppedState) return 0;
|
||||||
|
return parent.width * audioMessage.position / audioMessage.duration;
|
||||||
|
}
|
||||||
|
color: Theme.palette.directColor5
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: handle
|
||||||
|
width: 10
|
||||||
|
height: 10
|
||||||
|
color: Theme.palette.directColor1
|
||||||
|
radius: 10
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
x: progress.width
|
||||||
|
state: "default"
|
||||||
|
|
||||||
|
states: State {
|
||||||
|
name: "pressed"
|
||||||
|
when: handleMouseArea.pressed
|
||||||
|
PropertyChanges {
|
||||||
|
target: handle;
|
||||||
|
scale: 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transitions: Transition {
|
||||||
|
NumberAnimation {
|
||||||
|
properties: "scale";
|
||||||
|
duration: 100;
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: handleMouseArea
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
anchors.fill: parent
|
||||||
|
drag.target: parent
|
||||||
|
drag.axis: Drag.XAxis
|
||||||
|
drag.minimumX: 0
|
||||||
|
drag.maximumX: parent.parent.width
|
||||||
|
onPressed: {
|
||||||
|
handle.state = "pressed"
|
||||||
|
if(audioMessage.playbackState === Audio.PlayingState) {
|
||||||
|
audioMessage.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
handle.state = "default"
|
||||||
|
audioMessage.seek(audioMessage.duration * handle.x / parent.parent.width)
|
||||||
|
audioMessage.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick.Layouts 1.14
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: editText
|
||||||
|
|
||||||
|
property alias inputComponent: chatInputLoader.sourceComponent
|
||||||
|
|
||||||
|
property string cancelButtonText: ""
|
||||||
|
property string saveButtonText: ""
|
||||||
|
property string msgText: ""
|
||||||
|
|
||||||
|
signal cancelEditClicked()
|
||||||
|
signal editCompleted(var newMsgText)
|
||||||
|
|
||||||
|
height: childrenRect.height
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 4
|
||||||
|
Loader {
|
||||||
|
id: chatInputLoader
|
||||||
|
// To-Do: Move to StatusChatInput once its moved to StatusQ
|
||||||
|
sourceComponent: StatusInput {
|
||||||
|
width: editText.width
|
||||||
|
input.placeholderText: ""
|
||||||
|
input.text: msgText
|
||||||
|
input.implicitHeight: 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
spacing: 4
|
||||||
|
StatusFlatButton {
|
||||||
|
id: cancelBtn
|
||||||
|
text: cancelButtonText
|
||||||
|
size: StatusBaseButton.Size.Small
|
||||||
|
onClicked: cancelEditClicked()
|
||||||
|
}
|
||||||
|
StatusButton {
|
||||||
|
id: saveBtn
|
||||||
|
text: saveButtonText
|
||||||
|
size: StatusBaseButton.Size.Small
|
||||||
|
enabled: chatInputLoader.item.input.text.trim().length > 0
|
||||||
|
onClicked: editCompleted(chatInputLoader.item.input.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtGraphicalEffects 1.13
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: imageContainer
|
||||||
|
|
||||||
|
enum ShapeType {
|
||||||
|
ROUNDED = 0,
|
||||||
|
LEFT_ROUNDED = 1,
|
||||||
|
RIGHT_ROUNDED = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
property alias imageAlias: imageMessage
|
||||||
|
|
||||||
|
property bool isAppWindowActive: false
|
||||||
|
property url source: ""
|
||||||
|
property bool allCornersRounded: false
|
||||||
|
property bool isLeftCorner: true
|
||||||
|
property int imageWidth: 350
|
||||||
|
property int shapeType: -1
|
||||||
|
|
||||||
|
property string loadingImageText: ""
|
||||||
|
property string errorLoadingImageText: ""
|
||||||
|
|
||||||
|
signal clicked(var image, var mouse)
|
||||||
|
|
||||||
|
width: loadingImage.visible ? loadingImage.width : imageMessage.width
|
||||||
|
height: loadingImage.visible ? loadingImage.height : imageMessage.paintedHeight
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: _internal
|
||||||
|
property bool isAnimated: !!source && source.toString().endsWith('.gif')
|
||||||
|
property bool pausePlaying: false
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedImage {
|
||||||
|
id: imageMessage
|
||||||
|
|
||||||
|
width: sourceSize.width > imageWidth ? imageWidth : sourceSize.width
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
source: imageContainer.source
|
||||||
|
playing: _internal.isAnimated && isAppWindowActive && !_internal.pausePlaying
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Item {
|
||||||
|
width: imageMessage.width
|
||||||
|
height: imageMessage.height
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
width: imageMessage.width
|
||||||
|
height: imageMessage.height
|
||||||
|
radius: 16
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
radius: 4
|
||||||
|
visible: shapeType === StatusImageMessage.ShapeType.LEFT_ROUNDED //!isLeftCorner && !allCornersRounded
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
radius: 4
|
||||||
|
visible: shapeType === StatusImageMessage.ShapeType.RIGHT_ROUNDED //isLeftCorner && !allCornersRounded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
if (imageContainer.isAnimated) {
|
||||||
|
// FIXME the ListView completely removes Items that scroll out of view
|
||||||
|
// so when we scroll backto the image, it gets reloaded and playing is reset
|
||||||
|
_internal.pausePlaying = ! _internal.pausePlaying
|
||||||
|
return
|
||||||
|
}
|
||||||
|
imageContainer.clicked(imageMessage, mouse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: loadingImage
|
||||||
|
visible: imageMessage.status === Image.Loading
|
||||||
|
|| imageMessage.status === Image.Error
|
||||||
|
width: parent.width
|
||||||
|
height: width
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.palette.baseColor2
|
||||||
|
radius: 8
|
||||||
|
|
||||||
|
StatusBaseText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: imageMessage.status === Image.Error ? errorLoadingImageText: loadingImageText
|
||||||
|
color: imageMessage.status === Image.Error?
|
||||||
|
Theme.palette.dangerColor1 :
|
||||||
|
Theme.palette.directColor1
|
||||||
|
font.pixelSize: 15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
import QtQuick 2.14
|
||||||
|
import QtQuick.Layouts 1.14
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
import StatusQ.Components 0.1
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: statusMessageHeader
|
||||||
|
|
||||||
|
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 resendText: ""
|
||||||
|
property bool showResendButton: false
|
||||||
|
property StatusIconSettings icon1: StatusIconSettings {
|
||||||
|
width: dummyImage.width
|
||||||
|
height: dummyImage.height
|
||||||
|
rotation: 0
|
||||||
|
color: Theme.palette.indirectColor1
|
||||||
|
background: StatusIconBackgroundSettings {
|
||||||
|
width: 10
|
||||||
|
height: 10
|
||||||
|
color: Theme.palette.primaryColor1
|
||||||
|
}
|
||||||
|
// only used to get implicit width and height from the actual image
|
||||||
|
property Image dummyImage: Image {
|
||||||
|
source: icon1.name ? "../../../../assets/img/icons/" + icon1.name + ".svg": ""
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property StatusIconSettings icon2: StatusIconSettings {
|
||||||
|
width: dummyImage.width
|
||||||
|
height: dummyImage.height
|
||||||
|
rotation: 0
|
||||||
|
color: Theme.palette.primaryColor1
|
||||||
|
background: StatusIconBackgroundSettings {
|
||||||
|
width: 10
|
||||||
|
height: 10
|
||||||
|
color: Theme.palette.indirectColor1
|
||||||
|
}
|
||||||
|
// only used to get implicit width and height from the actual image
|
||||||
|
property Image dummyImage: Image {
|
||||||
|
source: icon2.name ? "../../../../assets/img/icons/" + icon2.name + ".svg": ""
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signal clicked()
|
||||||
|
signal resendClicked()
|
||||||
|
|
||||||
|
height: childrenRect.height
|
||||||
|
width: primaryDisplayName.width + (secondaryDisplayName.visible ? secondaryDisplayName.width + header.spacing : 0)
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: header
|
||||||
|
spacing: 4
|
||||||
|
TextEdit {
|
||||||
|
id: primaryDisplayName
|
||||||
|
font.family: Theme.palette.baseFont.name
|
||||||
|
font.weight: Font.Medium
|
||||||
|
font.pixelSize: 15
|
||||||
|
font.underline: mouseArea.containsMouse
|
||||||
|
readOnly: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
selectByMouse: true
|
||||||
|
color: Theme.palette.primaryColor1
|
||||||
|
text: displayName
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
hoverEnabled: true
|
||||||
|
onClicked: {
|
||||||
|
statusMessageHeader.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
}
|
||||||
|
StatusRoundIcon {
|
||||||
|
icon.background.width: icon1.background.width
|
||||||
|
icon.background.height: icon1.background.height
|
||||||
|
icon.background.color: icon1.background.color
|
||||||
|
icon.width: icon1.width
|
||||||
|
icon.height: icon1.height
|
||||||
|
icon.name: icon1.name
|
||||||
|
icon.rotation: icon1.rotation
|
||||||
|
icon.color: icon1.color
|
||||||
|
visible: !!icon.name
|
||||||
|
}
|
||||||
|
StatusRoundIcon {
|
||||||
|
icon.background.width: icon2.background.width
|
||||||
|
icon.background.height: icon2.background.height
|
||||||
|
icon.background.color: icon2.background.color
|
||||||
|
icon.width: icon2.width
|
||||||
|
icon.height: icon2.height
|
||||||
|
icon.name: icon2.name
|
||||||
|
icon.rotation: icon2.rotation
|
||||||
|
icon.color: icon2.color
|
||||||
|
visible: !!icon.name
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
id: secondaryDisplayName
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
font.pixelSize: 10
|
||||||
|
text: secondaryName
|
||||||
|
visible: !!text
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
id: dotSeparator1
|
||||||
|
Layout.fillHeight: true
|
||||||
|
font.pixelSize: 10
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
text: "."
|
||||||
|
visible: secondaryDisplayName.visible
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
id: tertiaryDetailText
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.maximumWidth: 58
|
||||||
|
font.pixelSize: 10
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
text: tertiaryDetail
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
id: dotSeparator2
|
||||||
|
Layout.fillHeight: true
|
||||||
|
font.pixelSize: 10
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
text: "."
|
||||||
|
visible: tertiaryDetailText.visible
|
||||||
|
}
|
||||||
|
StatusTimeStampLabel {
|
||||||
|
id: timestampText
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
color: Theme.palette.dangerColor1
|
||||||
|
font.pixelSize: 12
|
||||||
|
text: statusMessageHeader.resendText
|
||||||
|
visible: showResendButton && !!timestampText.text
|
||||||
|
MouseArea {
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: statusMessageHeader.resendClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
import QtGraphicalEffects 1.13
|
||||||
|
import QtQuick.Layouts 1.14
|
||||||
|
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: buttonsContainer
|
||||||
|
|
||||||
|
property list<Item> quickActions
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: _internal
|
||||||
|
readonly property int containerMargin: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
width: buttonRow.width + _internal.containerMargin * 2
|
||||||
|
height: 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
|
||||||
|
horizontalOffset: 0
|
||||||
|
verticalOffset: 2
|
||||||
|
source: buttonsContainer
|
||||||
|
radius: 10
|
||||||
|
samples: 15
|
||||||
|
color: Theme.palette.dropShadow
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: buttonRow
|
||||||
|
spacing: _internal.containerMargin
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: _internal.containerMargin
|
||||||
|
anchors.verticalCenter: buttonsContainer.verticalCenter
|
||||||
|
height: parent.height - 2 * _internal.containerMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
onQuickActionsChanged: {
|
||||||
|
for (let idx in quickActions) {
|
||||||
|
quickActions[idx].parent = buttonRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
import QtQuick 2.14
|
||||||
|
import QtQuick.Shapes 1.13
|
||||||
|
import QtGraphicalEffects 1.13
|
||||||
|
import QtQuick.Layouts 1.14
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
import StatusQ.Components 0.1
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: chatReply
|
||||||
|
|
||||||
|
property StatusMessageDetails replyDetails
|
||||||
|
property string audioMessageInfoText: ""
|
||||||
|
|
||||||
|
signal replyProfileClicked()
|
||||||
|
|
||||||
|
active: visible
|
||||||
|
|
||||||
|
sourceComponent: RowLayout {
|
||||||
|
id: replyLayout
|
||||||
|
spacing: 8
|
||||||
|
Shape {
|
||||||
|
id: replyCorner
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.leftMargin: 35
|
||||||
|
Layout.topMargin: profileImage.height/2
|
||||||
|
Layout.preferredWidth: 20
|
||||||
|
Layout.preferredHeight: messageLayout.height - replyCorner.Layout.topMargin
|
||||||
|
asynchronous: true
|
||||||
|
antialiasing: true
|
||||||
|
ShapePath {
|
||||||
|
strokeColor: Qt.hsla(Theme.palette.baseColor1.hslHue, Theme.palette.baseColor1.hslSaturation, Theme.palette.baseColor1.hslLightness, 0.4)
|
||||||
|
strokeWidth: 3
|
||||||
|
fillColor: "transparent"
|
||||||
|
capStyle: ShapePath.RoundCap
|
||||||
|
joinStyle: ShapePath.RoundJoin
|
||||||
|
startX: 20
|
||||||
|
startY: 0
|
||||||
|
PathLine { x: 10; y: 0 }
|
||||||
|
PathArc {
|
||||||
|
x: 0; y: 10
|
||||||
|
radiusX: 13
|
||||||
|
radiusY: 13
|
||||||
|
direction: PathArc.Counterclockwise
|
||||||
|
}
|
||||||
|
PathLine { x: 0; y: messageLayout.height}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
id: messageLayout
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.topMargin: 4
|
||||||
|
RowLayout {
|
||||||
|
StatusSmartIdenticon {
|
||||||
|
id: profileImage
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
image: replyDetails.profileImage
|
||||||
|
name: replyDetails.displayName
|
||||||
|
MouseArea {
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: replyProfileClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextEdit {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
selectionColor: Theme.palette.primaryColor3
|
||||||
|
selectedTextColor: Theme.palette.directColor1
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.weight: Font.Medium
|
||||||
|
selectByMouse: true
|
||||||
|
readOnly: true
|
||||||
|
text: replyDetails.displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StatusTextMessage {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
textField.text: replyDetails.messageText
|
||||||
|
textField.font.pixelSize: 13
|
||||||
|
textField.color: Theme.palette.baseColor1
|
||||||
|
textField.height: 18
|
||||||
|
clip: true
|
||||||
|
visible: !!replyDetails.messageText
|
||||||
|
}
|
||||||
|
StatusImageMessage {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: imageAlias.paintedHeight
|
||||||
|
imageWidth: 56
|
||||||
|
source: replyDetails.contentType === StatusMessage.ContentType.Image ? replyDetails.messageContent : ""
|
||||||
|
visible: replyDetails.contentType === StatusMessage.ContentType.Image
|
||||||
|
shapeType: StatusImageMessage.ShapeType.ROUNDED
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
visible: replyDetails.contentType === StatusMessage.ContentType.Sticker
|
||||||
|
StatusSticker {
|
||||||
|
image.width: 48
|
||||||
|
image.height: 48
|
||||||
|
image.source: replyDetails.messageContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 22
|
||||||
|
visible: replyDetails.contentType === StatusMessage.ContentType.Audio
|
||||||
|
StatusAudioMessage {
|
||||||
|
id: audioMessage
|
||||||
|
anchors.left: parent.left
|
||||||
|
width: 125
|
||||||
|
height: 22
|
||||||
|
isPreview: true
|
||||||
|
audioSource: replyDetails.messageContent
|
||||||
|
audioMessageInfoText: chatReply.audioMessageInfoText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
import QtQuick.Layouts 1.14
|
||||||
|
import QtGraphicalEffects 1.13
|
||||||
|
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
property string pinnedMsgInfoText: ""
|
||||||
|
property string pinnedBy: ""
|
||||||
|
|
||||||
|
active: visible
|
||||||
|
|
||||||
|
sourceComponent: Rectangle {
|
||||||
|
height: 24
|
||||||
|
width: layout.width + 16
|
||||||
|
color: Theme.palette.pinColor2
|
||||||
|
radius: 12
|
||||||
|
RowLayout {
|
||||||
|
id: layout
|
||||||
|
anchors.centerIn: parent
|
||||||
|
StatusIcon {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.preferredWidth: 16
|
||||||
|
Layout.preferredHeight: 16
|
||||||
|
color: Theme.palette.pinColor1
|
||||||
|
icon: "tiny/pin"
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.leftMargin: -4
|
||||||
|
color: Theme.palette.directColor1
|
||||||
|
font.pixelSize: 13
|
||||||
|
text: pinnedMsgInfoText
|
||||||
|
}
|
||||||
|
StatusBaseText {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.leftMargin: -4
|
||||||
|
color: Theme.palette.directColor1
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.weight: Font.Medium
|
||||||
|
text: pinnedBy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtGraphicalEffects 1.13
|
||||||
|
|
||||||
|
import StatusQ.Components 0.1
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: statusSticker
|
||||||
|
|
||||||
|
property bool noHover: false
|
||||||
|
property bool noMouseArea: false
|
||||||
|
property StatusImageSettings image: StatusImageSettings {
|
||||||
|
width: 140
|
||||||
|
height: 140
|
||||||
|
}
|
||||||
|
|
||||||
|
signal loaded()
|
||||||
|
signal clicked()
|
||||||
|
|
||||||
|
active: visible
|
||||||
|
|
||||||
|
sourceComponent: Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
color: Theme.palette.baseColor2
|
||||||
|
radius: 16
|
||||||
|
|
||||||
|
width: image.width
|
||||||
|
height: image.height
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
// From the documentation (https://doc.qt.io/qt-5/qml-qtquick-image.html#sourceSize-prop)
|
||||||
|
// Note: Changing this property dynamically causes the image source to
|
||||||
|
// be reloaded, potentially even from the network, if it is not in the
|
||||||
|
// disk cache.
|
||||||
|
const oldSource = sticker.source
|
||||||
|
sticker.cache = false
|
||||||
|
sticker.sourceSize.width += 1
|
||||||
|
sticker.sourceSize.width -= 1
|
||||||
|
sticker.cache = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: loader
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: sticker
|
||||||
|
anchors.fill: parent
|
||||||
|
horizontalAlignment: Image.AlignHCenter
|
||||||
|
verticalAlignment: Image.AlignVCenter
|
||||||
|
cache: true
|
||||||
|
source: image.source
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Ready) {
|
||||||
|
statusSticker.loaded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
enabled: !noMouseArea && (sticker.status === Image.Ready)
|
||||||
|
cursorShape: noHover ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: statusSticker.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: loadingIndicator
|
||||||
|
StatusLoadingIndicator {
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: reload
|
||||||
|
StatusIcon {
|
||||||
|
icon: "refresh"
|
||||||
|
color: Theme.palette.directColor1
|
||||||
|
mipmap: false
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "loading"
|
||||||
|
when: sticker.status === Image.Loading
|
||||||
|
PropertyChanges {
|
||||||
|
target: loader
|
||||||
|
sourceComponent: loadingIndicator
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "error"
|
||||||
|
when: sticker.status === Image.Error
|
||||||
|
PropertyChanges {
|
||||||
|
target: loader
|
||||||
|
sourceComponent: reload
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "ready"
|
||||||
|
when: sticker.status === Image.Ready
|
||||||
|
PropertyChanges {
|
||||||
|
target: root
|
||||||
|
color: "transparent"
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: loader
|
||||||
|
sourceComponent: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
|
||||||
|
import StatusQ.Components 0.1
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: textMessage
|
||||||
|
|
||||||
|
property int contentType: 0
|
||||||
|
property alias textField: chatText
|
||||||
|
|
||||||
|
signal linkActivated(url link)
|
||||||
|
|
||||||
|
implicitHeight: showMoreLoader.active ? childrenRect.height : chatText.height
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: _internal
|
||||||
|
property bool readMore: false
|
||||||
|
property bool veryLongChatText: chatText.length > 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEdit {
|
||||||
|
id: chatText
|
||||||
|
visible: !showMoreLoader.active || _internal.readMore
|
||||||
|
selectedTextColor: Theme.palette.directColor1
|
||||||
|
selectionColor: Theme.palette.primaryColor3
|
||||||
|
color: Theme.palette.directColor1
|
||||||
|
font.family: Theme.palette.baseFont.name
|
||||||
|
font.pixelSize: 15
|
||||||
|
textFormat: Text.RichText
|
||||||
|
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)
|
||||||
|
onLinkHovered: {
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: mask
|
||||||
|
anchors.fill: chatText
|
||||||
|
active: showMoreLoader.active
|
||||||
|
visible: false
|
||||||
|
sourceComponent: LinearGradient {
|
||||||
|
start: Qt.point(0, 0)
|
||||||
|
end: Qt.point(0, chatText.height)
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: "white" }
|
||||||
|
GradientStop { position: 0.85; color: "white" }
|
||||||
|
GradientStop { position: 1; color: "transparent" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: opMask
|
||||||
|
active: showMoreLoader.active && !_internal.readMore
|
||||||
|
anchors.fill: chatText
|
||||||
|
sourceComponent: OpacityMask {
|
||||||
|
source: chatText
|
||||||
|
maskSource: mask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: showMoreLoader
|
||||||
|
active: _internal.veryLongChatText
|
||||||
|
anchors.top: chatText.bottom
|
||||||
|
anchors.topMargin: -10
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
sourceComponent: StatusRoundButton {
|
||||||
|
implicitWidth: 24
|
||||||
|
implicitHeight: 24
|
||||||
|
type: StatusRoundButton.Type.Secondary
|
||||||
|
icon.name: _internal.readMore ? "chevron-up": "chevron-down"
|
||||||
|
onClicked: {
|
||||||
|
_internal.readMore = !_internal.readMore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
import QtQuick.Layouts 1.14
|
||||||
|
|
||||||
|
import StatusQ.Controls 0.1
|
||||||
|
import StatusQ.Core 0.1
|
||||||
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
StatusBaseText {
|
||||||
|
id: timestampLabe;
|
||||||
|
property alias tooltip: tooltip
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
color: Theme.palette.baseColor1
|
||||||
|
font.pixelSize: 10
|
||||||
|
visible: !!text
|
||||||
|
StatusToolTip {
|
||||||
|
id: tooltip
|
||||||
|
visible: hhandler.hovered && !!text
|
||||||
|
maxWidth: 350
|
||||||
|
}
|
||||||
|
HoverHandler {
|
||||||
|
id: hhandler
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,3 +23,5 @@ StatusMacWindowButtons 0.1 StatusMacWindowButtons.qml
|
||||||
StatusListItemBadge 0.1 StatusListItemBadge.qml
|
StatusListItemBadge 0.1 StatusListItemBadge.qml
|
||||||
StatusExpandableItem 0.1 StatusExpandableItem.qml
|
StatusExpandableItem 0.1 StatusExpandableItem.qml
|
||||||
StatusSmartIdenticon 0.1 StatusSmartIdenticon.qml
|
StatusSmartIdenticon 0.1 StatusSmartIdenticon.qml
|
||||||
|
StatusMessage 0.1 StatusMessage.qml
|
||||||
|
StatusMessageDetails 0.1 StatusMessageDetails.qml
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<svg width="14" height="12" viewBox="0 0 14 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.99971 0.668945C6.71994 0.668945 6.49314 0.895744 6.49314 1.17551V10.8238C6.49314 11.1036 6.71994 11.3304 6.99971 11.3304C7.27948 11.3304 7.50628 11.1036 7.50628 10.8238V1.17552C7.50628 0.895744 7.27948 0.668945 6.99971 0.668945Z" fill="#939BA1"/>
|
||||||
|
<path d="M2.38654 2.97442C2.38654 2.69465 2.61333 2.46785 2.89311 2.46785C3.17288 2.46785 3.39968 2.69465 3.39968 2.97442V9.02493C3.39968 9.3047 3.17288 9.5315 2.89311 9.5315C2.61333 9.5315 2.38654 9.3047 2.38654 9.02493V2.97442Z" fill="#939BA1"/>
|
||||||
|
<path d="M4.43992 4.43813C4.43992 4.15836 4.66672 3.93156 4.94649 3.93156C5.22626 3.93156 5.45306 4.15836 5.45306 4.43813V7.56126C5.45306 7.84103 5.22626 8.06783 4.94649 8.06783C4.66672 8.06783 4.43992 7.84103 4.43992 7.56126V4.43813Z" fill="#939BA1"/>
|
||||||
|
<path d="M0.333313 5.35146C0.333313 5.07169 0.560112 4.84489 0.839883 4.84489C1.11965 4.84489 1.34645 5.07169 1.34645 5.35146V6.6479C1.34645 6.92767 1.11965 7.15447 0.839883 7.15447C0.560112 7.15447 0.333313 6.92767 0.333313 6.6479V5.35146Z" fill="#939BA1"/>
|
||||||
|
<path d="M9.0531 3.93156C8.77333 3.93156 8.54653 4.15836 8.54653 4.43813V7.56123C8.54653 7.841 8.77333 8.0678 9.0531 8.0678C9.33287 8.0678 9.55967 7.841 9.55967 7.56123V4.43813C9.55967 4.15836 9.33287 3.93156 9.0531 3.93156Z" fill="#939BA1"/>
|
||||||
|
<path d="M11.1063 2.46785C10.8266 2.46785 10.5998 2.69465 10.5998 2.97442V9.02493C10.5998 9.3047 10.8266 9.5315 11.1063 9.5315C11.3861 9.5315 11.6129 9.3047 11.6129 9.02493V2.97442C11.6129 2.69465 11.3861 2.46785 11.1063 2.46785Z" fill="#939BA1"/>
|
||||||
|
<path d="M12.6531 5.35146C12.6531 5.07169 12.8799 4.84489 13.1597 4.84489C13.4395 4.84489 13.6663 5.07169 13.6663 5.35146V6.64791C13.6663 6.92769 13.4395 7.15448 13.1597 7.15448C12.8799 7.15448 12.6531 6.92769 12.6531 6.64791V5.35146Z" fill="#939BA1"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="2" height="8" viewBox="0 0 2 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.78125 6.65625C1.78125 7.08772 1.43147 7.4375 1 7.4375C0.568528 7.4375 0.21875 7.08772 0.21875 6.65625C0.21875 6.22478 0.568528 5.875 1 5.875C1.43147 5.875 1.78125 6.22478 1.78125 6.65625ZM1 0.875C0.654822 0.875 0.375 1.15482 0.375 1.5V4.3125C0.375 4.65768 0.654822 4.9375 1 4.9375C1.34518 4.9375 1.625 4.65768 1.625 4.3125V1.5C1.625 1.15482 1.34518 0.875 1 0.875Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 531 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="6" height="4" viewBox="0 0 6 4" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.21097 2.79222L4.73854 0.255725C4.91216 0.0814879 5.19307 0.0808952 5.36869 0.257127C5.54308 0.432138 5.54399 0.714976 5.37008 0.889498L2.52534 3.74428C2.43901 3.83091 2.32615 3.87462 2.2129 3.875C2.09629 3.87412 1.98311 3.8311 1.89811 3.7458L0.629196 2.47241C0.456409 2.29901 0.456496 2.01779 0.632108 1.84156C0.806504 1.66655 1.09029 1.66758 1.26074 1.83864L2.21097 2.79222Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 543 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="6" height="8" viewBox="0 0 6 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.99996 3.6875C3.77661 3.6875 4.40621 3.0579 4.40621 2.28125C4.40621 1.5046 3.77661 0.875 2.99996 0.875C2.22331 0.875 1.59371 1.5046 1.59371 2.28125C1.59371 3.0579 2.22331 3.6875 2.99996 3.6875Z" fill="white"/>
|
||||||
|
<path d="M0.577101 6.50632C0.852422 5.42485 1.83278 4.625 2.99996 4.625C4.16714 4.625 5.1475 5.42485 5.42282 6.50632C5.50798 6.84083 5.22014 7.125 4.87496 7.125H1.12496C0.779781 7.125 0.491942 6.84083 0.577101 6.50632Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 555 B |
Loading…
Reference in New Issue