fix(ChatMessagesView): Usage of `StatusMessage` WIP

This commit is contained in:
Igor Sirotin 2022-07-05 14:12:27 +04:00 committed by Igor Sirotin
parent 75ec2750b3
commit 42a1cf995c
52 changed files with 1307 additions and 4020 deletions

View File

@ -75,7 +75,7 @@ proc createMessageItemFromDto(self: Module, message: MessageDto, chatDetails: Ch
chatDetails.communityId, # we don't received community id via `activityCenterNotifications` api call
message.responseTo,
message.`from`,
contactDetails.displayName,
contactDetails.details.displayName,
contactDetails.details.localNickname,
contactDetails.icon,
contactDetails.isCurrentUser,
@ -93,7 +93,8 @@ proc createMessageItemFromDto(self: Module, message: MessageDto, chatDetails: Ch
message.links,
newTransactionParametersItem("","","","","","",-1,""),
message.mentionedUsersPks,
contactDetails.details.trustStatus
contactDetails.details.trustStatus,
contactDetails.details.ensVerified
))
method convertToItems*(

View File

@ -91,10 +91,11 @@ proc createFetchMoreMessagesItem(self: Module): Item =
messageType = -1,
sticker = "",
stickerPack = -1,
@[],
newTransactionParametersItem("","","","","","",-1,""),
@[],
TrustStatus.Unknown
links = @[],
transactionParameters = newTransactionParametersItem("","","","","","",-1,""),
mentionedUsersPks = @[],
senderTrustStatus = TrustStatus.Unknown,
senderEnsVerified = false
)
proc createChatIdentifierItem(self: Module): Item =
@ -128,10 +129,11 @@ proc createChatIdentifierItem(self: Module): Item =
messageType = -1,
sticker = "",
stickerPack = -1,
@[],
newTransactionParametersItem("","","","","","",-1,""),
@[],
TrustStatus.Unknown
links = @[],
transactionParameters = newTransactionParametersItem("","","","","","",-1,""),
mentionedUsersPks = @[],
senderTrustStatus = TrustStatus.Unknown,
senderEnsVerified = false
)
proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module): bool =
@ -177,7 +179,7 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
m.communityId,
m.responseTo,
m.`from`,
sender.displayName,
sender.details.displayName,
sender.details.localNickname,
sender.icon,
isCurrentUser,
@ -203,6 +205,7 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
m.transactionParameters.signature),
m.mentionedUsersPks(),
sender.details.trustStatus,
sender.details.ensVerified
)
for r in reactions:
@ -265,7 +268,7 @@ method messageAdded*(self: Module, message: MessageDto) =
message.communityId,
message.responseTo,
message.`from`,
sender.displayName,
sender.details.displayName,
sender.details.localNickname,
sender.icon,
isCurrentUser,
@ -291,6 +294,7 @@ method messageAdded*(self: Module, message: MessageDto) =
message.transactionParameters.signature),
message.mentionedUsersPks,
sender.details.trustStatus,
sender.details.ensVerified
)
self.view.model().insertItemBasedOnTimestamp(item)
@ -399,7 +403,7 @@ method updateContactDetails*(self: Module, contactId: string) =
let updatedContact = self.controller.getContactDetails(contactId)
for item in self.view.model().modelContactUpdateIterator(contactId):
if(item.senderId == contactId):
item.senderDisplayName = updatedContact.displayName
item.senderDisplayName = updatedContact.details.displayName
item.senderLocalName = updatedContact.details.localNickname
item.senderIcon = updatedContact.icon
item.senderIsAdded = updatedContact.details.added

View File

@ -167,7 +167,7 @@ proc buildPinnedMessageItem(self: Module, messageId: string, actionInitiatedBy:
m.communityId,
m.responseTo,
m.`from`,
contactDetails.displayName,
contactDetails.details.displayName,
contactDetails.details.localNickname,
contactDetails.icon,
isCurrentUser,
@ -193,6 +193,7 @@ proc buildPinnedMessageItem(self: Module, messageId: string, actionInitiatedBy:
m.transactionParameters.signature),
m.mentionedUsersPks,
contactDetails.details.trustStatus,
contactDetails.details.ensVerified
)
item.pinned = true
item.pinnedBy = actionInitiatedBy
@ -318,8 +319,9 @@ method onContactDetailsUpdated*(self: Module, contactId: string) =
let updatedContact = self.controller.getContactDetails(contactId)
for item in self.view.pinnedModel().modelContactUpdateIterator(contactId):
if(item.senderId == contactId):
item.senderDisplayName = updatedContact.displayName
item.senderDisplayName = updatedContact.details.displayName
item.senderLocalName = updatedContact.details.localNickname
item.senderEnsVerified = updatedContact.details.ensVerified
item.senderIcon = updatedContact.icon
item.senderTrustStatus = updatedContact.details.trustStatus
if(item.messageContainsMentions):
@ -329,7 +331,7 @@ method onContactDetailsUpdated*(self: Module, contactId: string) =
item.messageContainsMentions = m.containsContactMentions()
if(self.controller.getMyChatId() == contactId):
self.view.updateChatDetailsNameAndIcon(updatedContact.displayName, updatedContact.icon)
self.view.updateChatDetailsNameAndIcon(updatedContact.details.displayName, updatedContact.icon)
self.view.updateTrustStatus(updatedContact.details.trustStatus == TrustStatus.Untrustworthy)
method onNotificationsUpdated*(self: Module, hasUnreadMessages: bool, notificationCount: int) =

View File

@ -75,7 +75,7 @@ method onNewMessagesLoaded*(self: Module, messages: seq[MessageDto]) =
let status = toOnlineStatus(statusUpdateDto.statusType)
self.view.model().addItem(initMemberItem(
pubKey = m.`from`,
displayName = contactDetails.displayName,
displayName = contactDetails.details.displayName,
ensName = contactDetails.details.name, # is it correct?
localNickname = contactDetails.details.localNickname,
alias = contactDetails.details.alias,
@ -132,7 +132,7 @@ method addChatMember*(self: Module, member: ChatMember) =
let isMe = member.id == singletonInstance.userProfile.getPubKey()
let contactDetails = self.controller.getContactDetails(member.id)
var status = OnlineStatus.Online
var displayName = contactDetails.displayName
var displayName = contactDetails.details.displayName
if (isMe):
let currentUserStatus = intToEnum(singletonInstance.userProfile.getCurrentUserStatus(), StatusType.Unknown)
status = toOnlineStatus(currentUserStatus)
@ -142,7 +142,7 @@ method addChatMember*(self: Module, member: ChatMember) =
self.view.model().addItem(initMemberItem(
pubKey = member.id,
displayName = displayName,
displayName = contactDetails.details.displayName,
ensName = contactDetails.details.name,
localNickname = contactDetails.details.localNickname,
alias = contactDetails.details.alias,
@ -180,7 +180,7 @@ method onChatMemberUpdated*(self: Module, publicKey: string, admin: bool, joined
let contactDetails = self.controller.getContactDetails(publicKey)
self.view.model().updateItem(
pubKey = publicKey,
displayName = contactDetails.displayName,
displayName = contactDetails.details.displayName,
ensName = contactDetails.details.name,
localNickname = contactDetails.details.localNickname,
alias = contactDetails.details.alias,

View File

@ -65,7 +65,7 @@ proc createChatItem(self: Module, chatDto: ChatDto): Item =
var itemType = item.Type.GroupChat
if(chatDto.chatType == ChatType.OneToOne):
let contactDetails = self.controller.getContactDetails(chatDto.id)
chatName = contactDetails.displayName
chatName = contactDetails.details.displayName
chatImage = contactDetails.icon
itemType = item.Type.OneToOneChat

View File

@ -37,6 +37,7 @@ type
transactionParameters: TransactionParametersItem
mentionedUsersPks: seq[string]
senderTrustStatus: TrustStatus
senderEnsVerified: bool
proc initItem*(
id,
@ -61,7 +62,8 @@ proc initItem*(
links: seq[string],
transactionParameters: TransactionParametersItem,
mentionedUsersPks: seq[string],
senderTrustStatus: TrustStatus
senderTrustStatus: TrustStatus,
senderEnsVerified: bool
): Item =
result = Item()
result.id = id
@ -93,6 +95,7 @@ proc initItem*(
result.gapFrom = 0
result.gapTo = 0
result.senderTrustStatus = senderTrustStatus
result.senderEnsVerified = senderEnsVerified
proc `$`*(self: Item): string =
result = fmt"""Item(
@ -120,6 +123,7 @@ proc `$`*(self: Item): string =
transactionParameters:{$self.transactionParameters},
mentionedUsersPks:{$self.mentionedUsersPks},
senderTrustStatus:{$self.senderTrustStatus},
senderEnsVerified: {self.senderEnsVerified},
)"""
proc id*(self: Item): string {.inline.} =
@ -167,6 +171,12 @@ proc senderTrustStatus*(self: Item): TrustStatus {.inline.} =
proc `senderTrustStatus=`*(self: Item, value: TrustStatus) {.inline.} =
self.senderTrustStatus = value
proc senderEnsVerified*(self: Item): bool {.inline.} =
self.senderEnsVerified
proc `senderEnsVerified=`*(self: Item, value: bool) {.inline.} =
self.senderEnsVerified = value
proc outgoingStatus*(self: Item): string {.inline.} =
self.outgoingStatus
@ -274,7 +284,8 @@ proc toJsonNode*(self: Item): JsonNode =
"editMode": self.editMode,
"isEdited": self.isEdited,
"links": self.links,
"mentionedUsersPks": self.mentionedUsersPks
"mentionedUsersPks": self.mentionedUsersPks,
"senderEnsVerified": self.senderEnsVerified
}
proc editMode*(self: Item): bool {.inline.} =

View File

@ -49,6 +49,10 @@ QtObject:
QtProperty[string] senderLocalName:
read = senderLocalName
proc senderEnsVerified*(self: MessageItem): bool {.slot.} = result = ?.self.messageItem.senderEnsVerified
QtProperty[bool] senderEnsVerified:
read = senderEnsVerified
proc amISender*(self: MessageItem): bool {.slot.} = result = ?.self.messageItem.amISender
QtProperty[bool] amISender:
read = amISender

View File

@ -38,6 +38,7 @@ type
TransactionParameters
MentionedUsersPks
SenderTrustStatus
SenderEnsVerified
QtObject:
type
@ -71,7 +72,7 @@ QtObject:
proc countChanged(self: Model) {.signal.}
proc getCount(self: Model): int {.slot.} =
self.items.len
QtProperty[int] count:
QtProperty[int]count:
read = getCount
notify = countChanged
@ -112,7 +113,8 @@ QtObject:
ModelRole.Links.int: "links",
ModelRole.TransactionParameters.int: "transactionParameters",
ModelRole.MentionedUsersPks.int: "mentionedUsersPks",
ModelRole.SenderTrustStatus.int: "senderTrustStatus"
ModelRole.SenderTrustStatus.int: "senderTrustStatus",
ModelRole.SenderEnsVerified.int: "senderEnsVerified"
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -205,6 +207,8 @@ QtObject:
}))
of ModelRole.MentionedUsersPks:
result = newQVariant(item.mentionedUsersPks.join(" "))
of ModelRole.SenderEnsVerified:
result = newQVariant(item.senderEnsVerified)
proc updateItemAtIndex(self: Model, index: int) =
let ind = self.createIndex(index, 0, nil)
@ -393,8 +397,12 @@ QtObject:
var roles: seq[int]
if(self.items[i].senderId == contactId):
roles = @[ModelRole.SenderDisplayName.int, ModelRole.SenderLocalName.int,
ModelRole.SenderIcon.int, ModelRole.SenderIsAdded.int, ModelRole.SenderTrustStatus.int]
roles = @[ModelRole.SenderDisplayName.int,
ModelRole.SenderLocalName.int,
ModelRole.SenderIcon.int,
ModelRole.SenderIsAdded.int,
ModelRole.SenderTrustStatus.int,
ModelRole.SenderEnsVerified.int]
if(self.items[i].pinnedBy == contactId):
roles.add(ModelRole.PinnedBy.int)
if(self.items[i].messageContainsMentions):

@ -1 +1 @@
Subproject commit 9f6f9905e988bb98689a4157dc7ad1fd42ea88c2
Subproject commit 6c107b5760fe33ef2fea3cee53a8039610d1024d

View File

@ -50,8 +50,8 @@ Item {
StyledText {
id: contactInfo
text: realChatType !== Constants.chatType.publicChat ?
StatusQUtils.Emoji.parse(Utils.removeStatusEns(Utils.filterXSS(name))) :
"#" + Utils.filterXSS(name)
StatusQUtils.Emoji.parse(Utils.removeStatusEns(StatusQUtils.Utils.filterXSS(name))) :
"#" + StatusQUtils.Utils.filterXSS(name)
anchors.left: contactImage.right
anchors.leftMargin: 4
color: textColor

View File

@ -24,7 +24,7 @@ Item {
}
StyledTextEdit {
text: Utils.getReplyMessageStyle(StatusQUtils.Emoji.parse(Utils.linkifyAndXSS(repliedMessageContent), StatusQUtils.Emoji.size.small), false)
text: Utils.getReplyMessageStyle(StatusQUtils.Emoji.parse(StatusQUtils.Utils.linkifyAndXSS(repliedMessageContent), StatusQUtils.Emoji.size.small), false)
textFormat: Text.RichText
height: 18
width: implicitWidth > 300 ? 300 : implicitWidth

View File

@ -10,11 +10,11 @@ StyledText {
color: Style.current.secondaryText
text: Utils.formatShortTime(timestamp)
font.pixelSize: Style.current.asideTextFontSize
property string timestamp
property int timestamp
StatusQ.StatusToolTip {
visible: hhandler.hovered
text: Utils.formatLongDateTime(parseInt(chatTime.timestamp, 10), RootStore.accountSensitiveSettings.isDDMMYYDateFormat, RootStore.accountSensitiveSettings.is24hTimeFormat)
text: Utils.formatLongDateTime(chatTime.timestamp, RootStore.accountSensitiveSettings.isDDMMYYDateFormat, RootStore.accountSensitiveSettings.is24hTimeFormat)
maxWidth: 350
}

View File

@ -101,14 +101,12 @@ Item {
if (mouse.button === Qt.RightButton) {
// Set parent, X & Y positions for the messageContextMenu
messageContextMenu.parent = this
messageContextMenu.setXPosition = function() { return 0; }
messageContextMenu.setYPosition = function() { return mouse.y + (Style.current.halfPadding/2); }
messageContextMenu.isProfile = true
messageContextMenu.myPublicKey = userProfile.pubKey
messageContextMenu.selectedUserPublicKey = model.pubKey
messageContextMenu.selectedUserDisplayName = model.displayName
messageContextMenu.selectedUserIcon = image.source
messageContextMenu.popup()
messageContextMenu.popup(4, 4)
} else if (mouse.button === Qt.LeftButton && !!messageContextMenu) {
Global.openProfilePopup(model.pubKey);
}

View File

@ -98,13 +98,16 @@ ModalPopup {
delegate: Item {
id: messageDelegate
property var listView: ListView.view
width: parent.width
width: ListView.view.width
height: messageItem.height
MessageView {
id: messageItem
store: popup.store
width: parent.width
rootStore: popup.store
messageStore: popup.messageStore
messageContextMenu: msgContextMenu
@ -113,9 +116,10 @@ ModalPopup {
senderId: model.senderId
senderDisplayName: model.senderDisplayName
senderLocalName: model.senderLocalName
senderEnsName: model.senderEnsVerified ? model.senderDisplayName : ""
senderIcon: model.senderIcon
amISender: model.amISender
message: model.messageText
messageText: model.messageText
messageImage: model.messageImage
messageTimestamp: model.timestamp
messageOutgoingStatus: model.outgoingStatus
@ -125,7 +129,6 @@ ModalPopup {
reactionsModel: model.reactions
senderTrustStatus: model.senderTrustStatus
linkUrls: model.links
isInPinnedPopup: true
transactionParams: model.transactionParameters
// This is possible since we have all data loaded before we load qml.
@ -136,7 +139,8 @@ ModalPopup {
nextMessageAsJsonObj: popup.messageStore? popup.messageStore.getMessageByIndexAsJson(index + 1) : {}
// Additional params
forceHoverHandler: !popup.messageToPin
isInPinnedPopup: true
disableHover: !!popup.messageToPin
}
MouseArea {

View File

@ -4,6 +4,7 @@ import QtQuick.Dialogs 1.3
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
@ -220,9 +221,9 @@ StatusModal {
let error = ""
if (isEdit) {
error = root.store.editCommunityCategory(root.categoryId, Utils.filterXSS(root.contentItem.categoryName.input.text), JSON.stringify(channels));
error = root.store.editCommunityCategory(root.categoryId, StatusQUtils.Utils.filterXSS(root.contentItem.categoryName.input.text), JSON.stringify(channels));
} else {
error = root.store.createCommunityCategory(Utils.filterXSS(root.contentItem.categoryName.input.text), JSON.stringify(channels));
error = root.store.createCommunityCategory(StatusQUtils.Utils.filterXSS(root.contentItem.categoryName.input.text), JSON.stringify(channels));
}
if (error) {

View File

@ -303,14 +303,14 @@ StatusDialog {
if (!isEdit) {
//scrollView.communityColor.color.toString().toUpperCase()
root.createCommunityChannel(Utils.filterXSS(nameInput.input.text),
Utils.filterXSS(descriptionTextArea.text),
root.createCommunityChannel(StatusQUtils.Utils.filterXSS(nameInput.input.text),
StatusQUtils.Utils.filterXSS(descriptionTextArea.text),
emoji,
colorDialog.color.toString().toUpperCase(),
root.categoryId)
} else {
root.editCommunityChannel(Utils.filterXSS(nameInput.input.text),
Utils.filterXSS(descriptionTextArea.text),
root.editCommunityChannel(StatusQUtils.Utils.filterXSS(nameInput.input.text),
StatusQUtils.Utils.filterXSS(descriptionTextArea.text),
emoji,
colorDialog.color.toString().toUpperCase(),
root.categoryId)

View File

@ -4,10 +4,10 @@ import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import utils 1.0
import shared.controls 1.0
import shared 1.0

View File

@ -135,49 +135,6 @@ QtObject {
return messageModule.toggleReaction(messageId, emojiId)
}
function lastTwoItems(nodes) {
return nodes.join(qsTr(" and "));
}
function showReactionAuthors(jsonArrayOfUsersReactedWithThisEmoji, emojiId) {
let listOfUsers = JSON.parse(jsonArrayOfUsersReactedWithThisEmoji)
if (listOfUsers.error) {
console.error("error parsing users who reacted to a message, error: ", obj.error)
return
}
let tooltip
if (listOfUsers.length === 1) {
tooltip = listOfUsers[0]
} else if (listOfUsers.length === 2) {
tooltip = 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);
tooltip = lastTwoItems([leftNode.join(", "), rightNode[0]])
}
tooltip += qsTr(" reacted with ");
let emojiHtml = StatusQUtils.Emoji.getEmojiFromId(emojiId);
if (emojiHtml) {
tooltip += emojiHtml;
}
return tooltip
}
function deleteMessage(messageId) {
if(!messageModule)
return

View File

@ -12,6 +12,7 @@ import "../controls"
import "../panels"
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
Item {
@ -20,22 +21,26 @@ Item {
height: childrenRect.height + dateGroupLbl.anchors.topMargin
property var store
property int previousNotificationIndex
property string previousNotificationTimestamp
property int previousNotificationTimestamp
property bool hideReadNotifications: false
property bool acCurrentFilterAll: false
DateGroup {
StatusDateGroupLabel {
id: dateGroupLbl
anchors.top: parent.top
anchors.topMargin: Style.current.halfPadding
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
height: visible ? implicitHeight : 0
visible: text !== ""
previousMessageIndex: root.previousNotificationIndex
previousMessageTimestamp: root.previousNotificationTimestamp
messageTimestamp: model.timestamp
isActivityCenterMessage: true
height: visible ? implicitHeight : 0
}
Rectangle {
id: groupRequestContent
property string timestamp: model.timestamp
property int timestamp: model.timestamp
visible: {
if (hideReadNotifications && model.read) {

View File

@ -124,11 +124,11 @@ Item {
MessageView {
id: notificationMessage
anchors.right: undefined
store: root.store
rootStore: root.store
messageStore: root.store.messageStore
messageId: model.id
senderDisplayName: model.message.senderDisplayName
message: model.message.messageText
messageText: model.message.messageText
responseToMessageWithId: model.message.responseToMessageWithId
senderId: model.message.senderId
senderLocalName: model.message.senderLocalName
@ -140,10 +140,10 @@ Item {
messageContentType: model.message.contentType
senderTrustStatus: model.message.senderTrustStatus
activityCenterMessage: true
read: model.read
activityCenterMessageRead: model.read
onImageClicked: Global.openImagePopup(image, root.messageContextMenu)
scrollToBottom: null
clickMessage: function (isProfileClick) {
messageClickHandler: {
if (isProfileClick) {
return Global.openProfilePopup(model.message.senderId);
}

View File

@ -91,11 +91,10 @@ ColumnLayout {
case Constants.chatType.publicChat:
return qsTr("Public chat")
case Constants.chatType.privateGroupChat:
let cnt = root.usersStore.usersModule.model.count
if(cnt > 1) return qsTr("%1 members").arg(cnt);
return qsTr("1 member");
const cnt = root.usersStore.usersModule.model.count
return qsTr("%n members(s)", "", cnt)
case Constants.chatType.communityChat:
return Utils.linkifyAndXSS(chatContentModule.chatDetails.description).trim()
return StatusQUtils.Utils.linkifyAndXSS(chatContentModule.chatDetails.description).trim()
default:
return ""
}
@ -161,10 +160,11 @@ ColumnLayout {
Component {
id: contactsSelector
GroupChatPanel {
sectionModule: root.chatSectionModule
sectionModule: chatSectionModule
chatContentModule: root.chatContentModule
rootStore: root.rootStore
maxHeight: root.height
onPanelClosed: topBar.toolbarComponent = statusChatInfoButton
}
}
@ -429,15 +429,16 @@ ColumnLayout {
id: chatMessages
Layout.fillWidth: true
Layout.fillHeight: true
store: root.rootStore
chatContentModule: root.chatContentModule
rootStore: root.rootStore
contactsStore: root.contactsStore
messageContextMenuInst: contextmenu
messageContextMenu: contextmenu
messageStore: messageStore
emojiPopup: root.emojiPopup
usersStore: root.usersStore
stickersLoaded: root.stickersLoaded
isChatBlocked: root.isBlocked
channelEmoji: chatContentModule.chatDetails.emoji || ""
channelEmoji: !chatContentModule ? "" : (chatContentModule.chatDetails.emoji || "")
isActiveChannel: root.isActiveChannel
onShowReplyArea: {
let obj = messageStore.getMessageByIdAsJson(messageId)
@ -455,9 +456,9 @@ ColumnLayout {
id: inputArea
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
Layout.fillWidth: true
Layout.preferredWidth: parent.width
height: chatInput.height
Layout.preferredHeight: height
Layout.preferredHeight: chatInput.implicitHeight
+ chatInput.anchors.topMargin
+ chatInput.anchors.bottomMargin
Loader {
id: loadingMessagesIndicator
@ -474,6 +475,10 @@ ColumnLayout {
StatusChatInput {
id: chatInput
anchors.fill: parent
anchors.margins: Style.current.smallPadding
store: root.rootStore
usersStore: root.usersStore

View File

@ -19,12 +19,18 @@ import shared.status 1.0
import shared.controls 1.0
import shared.views.chat 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import "../controls"
Item {
id: root
property var store
property var chatContentModule
property var rootStore
property var messageStore
property var usersStore
property var contactsStore
@ -37,7 +43,7 @@ Item {
property bool isChatBlocked: false
property bool isActiveChannel: false
property var messageContextMenuInst
property var messageContextMenu
property real scrollY: chatLogView.visibleArea.yPosition * chatLogView.contentHeight
property int newMessages: 0
@ -67,23 +73,23 @@ Item {
}
// Not Refactored Yet
// onNewMessagePushed: {
// if (!chatLogView.scrollToBottom()) {
// newMessages++
// }
// }
// onNewMessagePushed: {
// if (!chatLogView.scrollToBottom()) {
// newMessages++
// }
// }
}
Item {
id: loadingMessagesIndicator
visible: root.store.loadingHistoryMessagesInProgress
visible: root.rootStore.loadingHistoryMessagesInProgress
anchors.top: parent.top
anchors.left: parent.left
height: visible? 20 : 0
width: parent.width
Loader {
active: root.store.loadingHistoryMessagesInProgress
active: root.rootStore.loadingHistoryMessagesInProgress
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
sourceComponent: Component {
@ -105,14 +111,6 @@ Item {
spacing: 0
verticalLayoutDirection: ListView.BottomToTop
// This header and Connections is to create an invisible padding so that the chat identifier is at the top
// The Connections is necessary, because doing the check inside the header created a binding loop (the contentHeight includes the header height
// If the content height is smaller than the full height, we "show" the padding so that the chat identifier is at the top, otherwise we disable the Connections
header: Item {
height: 0
width: chatLogView.width
}
function checkHeaderHeight() {
if (!chatLogView.headerItem) {
return
@ -125,30 +123,70 @@ Item {
}
}
function scrollToBottom(force, caller) {
if (!force && !chatLogView.atYEnd) {
// User has scrolled up, we don't want to scroll back
return false
}
if (caller && caller !== chatLogView.itemAtIndex(chatLogView.count - 1)) {
// If we have a caller, only accept its request if it's the last message
return false
}
// Call this twice and with a timer since the first scroll to bottom might have happened before some stuff loads
// meaning that the scroll will not actually be at the bottom on switch
// Add a small delay because images, even though they say they say they are loaed, they aren't shown yet
Qt.callLater(chatLogView.positionViewAtBeginning)
timer.setTimeout(function() {
Qt.callLater(chatLogView.positionViewAtBeginning)
}, 100);
return true
}
model: messageStore.messagesModel
Component.onCompleted: chatLogView.scrollToBottom(true)
onContentYChanged: {
scrollDownButton.visible = contentHeight - (scrollY + height) > 400
let loadMore = scrollDownButton.visible && scrollY < 500
if(loadMore){
messageStore.loadMoreMessages()
}
}
ScrollBar.vertical: StatusScrollBar {
visible: chatLogView.visibleArea.heightRatio < 1
}
// Connections {
// id: contentHeightConnection
// enabled: true
// target: chatLogView
// onContentHeightChanged: {
// chatLogView.checkHeaderHeight()
// }
// onHeightChanged: {
// chatLogView.checkHeaderHeight()
// }
// }
// This header and Connections is to create an invisible padding so that the chat identifier is at the top
// The Connections is necessary, because doing the check inside the header created a binding loop (the contentHeight includes the header height
// If the content height is smaller than the full height, we "show" the padding so that the chat identifier is at the top, otherwise we disable the Connections
header: Item {
height: 0
width: chatLogView.width
}
// Connections {
// id: contentHeightConnection
// enabled: true
// target: chatLogView
// onContentHeightChanged: {
// chatLogView.checkHeaderHeight()
// }
// onHeightChanged: {
// chatLogView.checkHeaderHeight()
// }
// }
Timer {
id: timer
}
Button {
id: scrollDownButton
readonly property int buttonPadding: 5
id: scrollDownButton
visible: false
height: 32
width: nbMessages.width + arrowImage.width + 2 * Style.current.halfPadding + (nbMessages.visible ? scrollDownButton.buttonPadding : 0)
@ -160,6 +198,7 @@ Item {
border.width: 0
radius: 16
}
onClicked: {
newMessages = 0
scrollDownButton.visible = false
@ -202,50 +241,23 @@ Item {
}
}
function scrollToBottom(force, caller) {
if (!force && !chatLogView.atYEnd) {
// User has scrolled up, we don't want to scroll back
return false
}
if (caller && caller !== chatLogView.itemAtIndex(chatLogView.count - 1)) {
// If we have a caller, only accept its request if it's the last message
return false
}
// Call this twice and with a timer since the first scroll to bottom might have happened before some stuff loads
// meaning that the scroll will not actually be at the bottom on switch
// Add a small delay because images, even though they say they say they are loaed, they aren't shown yet
Qt.callLater(chatLogView.positionViewAtBeginning)
timer.setTimeout(function() {
Qt.callLater(chatLogView.positionViewAtBeginning)
}, 100);
return true
}
// Connections {
// Connections {
// Not Refactored Yet
// target: root.store.chatsModelInst
// target: root.rootStore.chatsModelInst
// onAppReady: {
// chatLogView.scrollToBottom(true)
// }
// }
onContentYChanged: {
scrollDownButton.visible = contentHeight - (scrollY + height) > 400
let loadMore = scrollDownButton.visible && scrollY < 500
if(loadMore){
messageStore.loadMoreMessages()
}
}
model: messageStore.messagesModel
Component.onCompleted: chatLogView.scrollToBottom(true)
// onAppReady: {
// chatLogView.scrollToBottom(true)
// }
// }
delegate: MessageView {
id: msgDelegate
width: ListView.view.width
height: implicitHeight
objectName: "chatMessageViewDelegate"
store: root.store
rootStore: root.rootStore
messageStore: root.messageStore
usersStore: root.usersStore
contactsStore: root.contactsStore
@ -255,7 +267,7 @@ Item {
isActiveChannel: root.isActiveChannel
isChatBlocked: root.isChatBlocked
messageContextMenu: messageContextMenuInst
messageContextMenu: root.messageContextMenu
itemIndex: index
messageId: model.id
@ -264,10 +276,11 @@ Item {
senderId: model.senderId
senderDisplayName: model.senderDisplayName
senderLocalName: model.senderLocalName
senderEnsName: model.senderEnsVerified ? model.senderDisplayName : ""
senderIcon: model.senderIcon
senderIsAdded: model.senderIsAdded
amISender: model.amISender
message: model.messageText
messageText: model.messageText
messageImage: model.messageImage
messageTimestamp: model.timestamp
messageOutgoingStatus: model.outgoingStatus
@ -282,6 +295,7 @@ Item {
isEdited: model.isEdited
linkUrls: model.links
transactionParams: model.transactionParameters
hasMention: model.mentionedUsersPks.split(" ").includes(root.rootStore.userProfileInst.pubKey)
gapFrom: model.gapFrom
gapTo: model.gapTo
@ -296,6 +310,7 @@ Item {
prevMsgTimestamp: model.prevMsgTimestamp
nextMessageIndex: model.nextMsgIndex
nextMessageAsJsonObj: messageStore.getMessageByIndexAsJson(model.nextMsgIndex)
onOpenStickerPackPopup: {
root.openStickerPackPopup(stickerPackId);
}
@ -304,7 +319,7 @@ Item {
root.showReplyArea(messageId, author)
}
onImageClicked: Global.openImagePopup(image, messageContextMenuInst)
onImageClicked: Global.openImagePopup(image, messageContextMenu)
stickersLoaded: root.stickersLoaded

View File

@ -10,6 +10,7 @@ import shared.popups 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Layout 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
@ -148,10 +149,10 @@ StatusAppTwoPanelLayout {
onEdited: {
const error = root.chatCommunitySectionModule.editCommunity(
Utils.filterXSS(item.name),
Utils.filterXSS(item.description),
Utils.filterXSS(item.introMessage),
Utils.filterXSS(item.outroMessage),
StatusQUtils.Utils.filterXSS(item.name),
StatusQUtils.Utils.filterXSS(item.description),
StatusQUtils.Utils.filterXSS(item.introMessage),
StatusQUtils.Utils.filterXSS(item.outroMessage),
item.options.requestToJoinEnabled ? Constants.communityChatOnRequestAccess : Constants.communityChatPublicAccess,
item.color.toString().toUpperCase(),
item.selectedTags,

View File

@ -9,6 +9,7 @@ import shared.popups 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
@ -160,10 +161,10 @@ StatusStackModal {
function createCommunity() {
const error = store.createCommunity({
name: Utils.filterXSS(nameInput.input.text),
description: Utils.filterXSS(descriptionTextInput.input.text),
introMessage: Utils.filterXSS(introMessageInput.input.text),
outroMessage: Utils.filterXSS(outroMessageInput.input.text),
name: StatusQUtils.Utils.filterXSS(nameInput.input.text),
description: StatusQUtils.Utils.filterXSS(descriptionTextInput.input.text),
introMessage: StatusQUtils.Utils.filterXSS(introMessageInput.input.text),
outroMessage: StatusQUtils.Utils.filterXSS(outroMessageInput.input.text),
color: colorPicker.color.toString().toUpperCase(),
tags: communityTagsPicker.selectedTags,
image: {

View File

@ -56,23 +56,25 @@ SettingsContentBase {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: paceholderMessage.height + paceholderMessage.anchors.margins*2
height: placeholderMessage.implicitHeight +
placeholderMessage.anchors.leftMargin +
placeholderMessage.anchors.rightMargin
radius: Style.current.radius
border.color: Style.current.border
color: Style.current.transparent
MessageView {
id: paceholderMessage
id: placeholderMessage
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.left
anchors.right: parent.right
anchors.margins: Style.current.smallPadding
isMessage: true
shouldRepeatHeader: true
messageTimestamp:Date.now()
messageTimestamp: Date.now()
senderDisplayName: "@vitalik"
senderIcon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAb0lEQVR4Ae3UQQqAIBRF0Wj9ba9Bq6l5JBQqfn/ngDMH3YS3AAB/tO3H+XRG3b9bR/+gVoREI2RapVXpfd5+X5oXERKNkHS+rk3tOpWkeREh0QiZVu91ql2zNC8iJBoh0yqtSqt1slpCghICANDPBc0ESPh0bHkHAAAAAElFTkSuQmCC"
message: qsTr("Blockchains will drop search costs, causing a kind of decomposition that allows you to have markets of entities that are horizontally segregated and vertically segregated.")
messageText: qsTr("Blockchains will drop search costs, causing a kind of decomposition that allows you to have markets of entities that are horizontally segregated and vertically segregated.")
messageContentType: Constants.messageContentType.messageType
placeholderMessage: true
}

View File

@ -130,56 +130,4 @@ QtObject {
return []
}
}
property var clickMessage: function(isProfileClick, isSticker = false, isImage = false, image = null, isEmoji = false, hideEmojiPicker = false, isReply = false, isRightClickOnImage = false, imageSource = "") {
if (placeholderMessage || activityCenterMessage) {
return
}
if (!isProfileClick) {
SelectedMessage.set(messageId, fromAuthor);
}
messageContextMenu.messageId = messageId
messageContextMenu.contentType = contentType
messageContextMenu.linkUrls = linkUrls;
messageContextMenu.isProfile = !!isProfileClick;
messageContextMenu.isCurrentUser = isCurrentUser
messageContextMenu.isText = isText
messageContextMenu.isSticker = isSticker;
messageContextMenu.isEmoji = isEmoji;
messageContextMenu.hideEmojiPicker = hideEmojiPicker;
messageContextMenu.pinnedMessage = pinnedMessage;
messageContextMenu.isCurrentUser = isCurrentUser;
messageContextMenu.isRightClickOnImage = isRightClickOnImage
messageContextMenu.imageSource = imageSource
messageContextMenu.onClickEdit = function() {isEdit = true}
//TODO remove dynamic scoping
if (isReply) {
let nickname = appMain.getUserNickname(repliedMessageAuthor)
messageContextMenu.show(repliedMessageAuthor, repliedMessageAuthorPubkey, repliedMessageUserImage, plainText, nickname, emojiReactionsModel);
} else {
let nickname = appMain.getUserNickname(fromAuthor)
messageContextMenu.show(userName, fromAuthor, profileImageSource, plainText, nickname, emojiReactionsModel);
}
messageContextMenu.x = messageContextMenu.setXPosition()
messageContextMenu.y = messageContextMenu.setYPosition()
}
function setHovered(messageId, hovered) {
if (hovered) {
hoveredMessage = messageId;
} else if (hoveredMessage === messageId) {
hoveredMessage = "";
}
}
function setMessageActive(messageId, active) {
if (active) {
activeMessage = messageId;
} else if (activeMessage === messageId) {
activeMessage = "";
}
}
}

View File

@ -140,7 +140,7 @@ Rectangle {
}
StyledText {
id: timeValue
text: Utils.formatLongDateTime(parseInt(timestamp) * 1000, RootStore.accountSensitiveSettings.isDDMMYYDateFormat, RootStore.accountSensitiveSettings.is24hTimeFormat)
text: Utils.formatLongDateTime(timestamp * 1000, RootStore.accountSensitiveSettings.isDDMMYYDateFormat, RootStore.accountSensitiveSettings.is24hTimeFormat)
font.pixelSize: Style.current.primaryTextFontSize
anchors.rightMargin: Style.current.smallPadding
}

View File

@ -1,60 +0,0 @@
import QtQuick 2.3
import shared 1.0
import shared.panels 1.0
import utils 1.0
StyledText {
property bool isActivityCenterMessage: false
property int previousMessageIndex: -1
property string previousMessageTimestamp
property string messageTimestamp
id: dateGroupLbl
font.pixelSize: 13
color: Style.current.secondaryText
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: isActivityCenterMessage ? undefined : parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: visible ? (isActivityCenterMessage ? Style.current.halfPadding : 20) : 0
anchors.left: parent.left
anchors.leftMargin: isActivityCenterMessage ? Style.current.padding : 0
text: {
if (previousMessageIndex === -1) return ""; // identifier
let now = new Date()
let yesterday = new Date()
yesterday.setDate(now.getDate()-1)
let currentMsgDate = new Date(parseInt(messageTimestamp, 10));
let prevMsgDate = previousMessageTimestamp === "" ? undefined : new Date(parseInt(previousMessageTimestamp, 10));
if (!!prevMsgDate && currentMsgDate.getDay() === prevMsgDate.getDay()) {
return ""
}
if (now.toDateString() === currentMsgDate.toDateString()) {
return qsTr("Today")
} else if (yesterday.toDateString() === currentMsgDate.toDateString()) {
return qsTr("Yesterday")
} else {
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()
}
}
visible: text !== ""
}

View File

@ -8,19 +8,18 @@ import utils 1.0
Item {
id: root
height: childrenRect.height + Style.current.smallPadding * 2
anchors.left: parent.left
anchors.right: parent.right
property int nextMessageIndex
property string nextMsgTimestamp
property double nextMsgTimestamp
signal clicked()
signal timerTriggered()
implicitHeight: childrenRect.height + Style.current.smallPadding * 2
QtObject {
id: d
readonly property string formattedDate: nextMessageIndex > -1 ? Utils.formatLongDate(nextMsgTimestamp * 1, RootStore.accountSensitiveSettings.isDDMMYYDateFormat) :
readonly property string formattedDate: nextMessageIndex > -1 ? Utils.formatLongDate(nextMsgTimestamp, RootStore.accountSensitiveSettings.isDDMMYYDateFormat) :
Utils.formatLongDate(undefined, RootStore.accountSensitiveSettings.isDDMMYYDateFormat)
}

View File

@ -12,9 +12,7 @@ Item {
signal clicked()
height: childrenRect.height + Style.current.smallPadding * 2
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: childrenRect.height + Style.current.smallPadding * 2
Separator {
id: sep1

View File

@ -1,53 +0,0 @@
import QtQuick 2.13
import utils 1.0
import shared 1.0
MouseArea {
id: mouseArea
z: 50
enabled: !placeholderMessage
//TODO remove dynamic scoping
// property bool isSticker: false
// property bool placeholderMessage: false
property bool isHovered: false
property bool stickersLoaded: false
property bool isMessageActive
property bool isActivityCenterMessage: false
property var messageContextMenu
property var messageContextMenuParent
signal openStickerPackPopup()
signal setMessageActive(string messageId, bool active)
signal clickMessage(bool isProfileClick, bool isSticker, bool isImage)
cursorShape: !enabled ? Qt.PointingHandCursor : undefined
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (isActivityCenterMessage) {
mouseArea.clickMessage(false, isSticker, false)
return
}
if (mouse.button === Qt.RightButton) {
if (!!mouseArea.messageContextMenu) {
// Set parent, X & Y positions for the messageContextMenu
messageContextMenu.parent = messageContextMenuParent;
messageContextMenu.setXPosition = function() { return (mouse.x)};
messageContextMenu.setYPosition = function() { return (mouse.y)};
}
mouseArea.clickMessage(false, isSticker, false)
if (typeof isMessageActive !== "undefined") {
setMessageActive(messageId, true)
}
return;
}
if (mouse.button === Qt.LeftButton && isSticker && stickersLoaded) {
if (isHovered) {
isHovered = false;
}
openStickerPackPopup();
return;
}
}
}

View File

@ -1,24 +0,0 @@
import QtQuick 2.3
import shared 1.0
import shared.panels 1.0
import utils 1.0
StyledText {
id: retryLbl
color: Style.current.red
text: qsTr("Resend")
font.pixelSize: Style.current.tertiaryTextFontSize
visible: isCurrentUser && (timeout || isExpired)
property bool isCurrentUser: false
property bool isExpired: false
property bool timeout: false
signal clicked()
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
retryLbl.clicked();
}
}
}

View File

@ -18,7 +18,6 @@ Loader {
property string image
property bool showRing: true
property bool interactive: true
property var messageContextMenu
property int colorId: Utils.colorIdForPubkey(pubkey)
property var colorHash: Utils.getColorHashAsJson(pubkey)
@ -51,12 +50,6 @@ Loader {
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
if (!!root.messageContextMenu) {
// Set parent, X & Y positions for the messageContextMenu
root.messageContextMenu.parent = root
root.messageContextMenu.setXPosition = function() { return root.width + 4 }
root.messageContextMenu.setYPosition = function() { return 0 }
}
root.clicked()
}
}

View File

@ -12,7 +12,6 @@ Item {
width: chatName.width + (ensOrAlias.visible ? ensOrAlias.width + ensOrAlias.anchors.leftMargin : 0)
property alias label: chatName
property var messageContextMenu
property string displayName
property string localName
property bool amISender
@ -41,12 +40,6 @@ Item {
root.isHovered = false
}
onClicked: {
if (!!root.messageContextMenu) {
// Set parent, X & Y positions for the messageContextMenu
root.messageContextMenu.parent = root
root.messageContextMenu.setXPosition = function() { return 0}
root.messageContextMenu.setYPosition = function() { return root.height + 4}
}
root.clickMessage(true);
}
}

View File

@ -1,111 +0,0 @@
import QtQuick 2.3
import QtMultimedia 5.14
import shared 1.0
import shared.panels 1.0
import shared.stores 1.0
import utils 1.0
Item {
property string audioSource: ""
height: 20
width: 350
Audio {
id: audioMessage
source: audioSource
store: RootStore
notifyInterval: 150
}
SVGImage {
id: playButton
source: audioMessage.playbackState == Audio.PlayingState ? Style.svg("icon-pause") : Style.svg("icon-play")
width: 15
height: 15
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
MouseArea {
id: playArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: {
if(audioMessage.playbackState === Audio.PlayingState){
audioMessage.pause();
} else {
audioMessage.play();
}
}
}
}
Rectangle {
height: 2
width: 300
color: Style.current.grey
anchors.verticalCenter: parent.verticalCenter
anchors.left: playButton.right
anchors.leftMargin: 20
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: Style.current.black
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
id: handle
width: 10
height: 10
color: Style.current.black
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);
}
}
}
}
}

View File

@ -1,213 +0,0 @@
import QtQuick 2.13
import QtGraphicalEffects 1.13
import StatusQ.Controls 0.1
import utils 1.0
import shared.popups 1.0
Rectangle {
id: buttonsContainer
property bool parentIsHovered: false
property bool isChatBlocked: false
property int containerMargin: 2
property int contentType: 2
property bool isCurrentUser: false
property bool isMessageActive: false
property var messageContextMenu
property bool isInPinnedPopup: false
property bool activityCenterMsg
property bool placeholderMsg
property string fromAuthor
property bool editBtnActive: false
property bool pinButtonActive: false
property bool deleteButtonActive: false
property bool pinnedMessage: false
property bool canPin: false
signal replyClicked(string messageId, string author)
signal hoverChanged(bool hovered)
signal setMessageActive(string messageId, bool active)
signal clickMessage(bool isProfileClick, bool isSticker, bool isImage, var image, bool isEmoji, bool hideEmojiPicker)
visible: !buttonsContainer.isChatBlocked &&
!buttonsContainer.placeholderMsg && !buttonsContainer.activityCenterMsg &&
(buttonsContainer.parentIsHovered || isMessageActive)
&& contentType !== Constants.messageContentType.transactionType
width: buttonRow.width + buttonsContainer.containerMargin * 2
height: 36
radius: Style.current.radius
color: Style.current.modalBackground
z: 52
layer.enabled: true
layer.effect: DropShadow {
width: buttonsContainer.width
height: buttonsContainer.height
x: buttonsContainer.x
y: buttonsContainer.y + 10
visible: buttonsContainer.visible
source: buttonsContainer
horizontalOffset: 0
verticalOffset: 2
radius: 10
samples: 15
color: "#22000000"
}
MouseArea {
anchors.fill: buttonsContainer
acceptedButtons: Qt.NoButton
hoverEnabled: true
onEntered: {
buttonsContainer.hoverChanged(true)
}
onExited: {
buttonsContainer.hoverChanged(false)
}
}
Row {
id: buttonRow
spacing: buttonsContainer.containerMargin
anchors.left: parent.left
anchors.leftMargin: buttonsContainer.containerMargin
anchors.verticalCenter: buttonsContainer.verticalCenter
height: parent.height - 2 * buttonsContainer.containerMargin
Loader {
active: !buttonsContainer.isInPinnedPopup
sourceComponent: StatusFlatRoundButton {
id: emojiBtn
width: 32
height: 32
icon.name: "reaction-b"
type: StatusFlatRoundButton.Type.Tertiary
tooltip.text: qsTr("Add reaction")
onClicked: {
setMessageActive(messageId, true)
// Set parent, X & Y positions for the messageContextMenu
buttonsContainer.messageContextMenu.parent = buttonsContainer
buttonsContainer.messageContextMenu.setXPosition = function() { return (-Math.abs(buttonsContainer.width - buttonsContainer.messageContextMenu.emojiContainer.width))}
buttonsContainer.messageContextMenu.setYPosition = function() { return (-buttonsContainer.messageContextMenu.height - 4)}
buttonsContainer.clickMessage(false, false, false, null, true, false)
}
onHoveredChanged: buttonsContainer.hoverChanged(this.hovered)
}
}
Loader {
active: !buttonsContainer.isInPinnedPopup
sourceComponent: StatusFlatRoundButton {
id: replyBtn
objectName: "replyToMessageButton"
width: 32
height: 32
icon.name: "reply"
type: StatusFlatRoundButton.Type.Tertiary
tooltip.text: qsTr("Reply")
onClicked: {
buttonsContainer.replyClicked(messageId, fromAuthor);
if (messageContextMenu.closeParentPopup) {
messageContextMenu.closeParentPopup()
}
}
onHoveredChanged: buttonsContainer.hoverChanged(this.hovered)
}
}
Loader {
active: buttonsContainer.editBtnActive && !buttonsContainer.isInPinnedPopup
sourceComponent: StatusFlatRoundButton {
id: editButton
width: 32
height: 32
icon.source: Style.svg("edit-message")
type: StatusFlatRoundButton.Type.Tertiary
tooltip.text: qsTr("Edit")
onClicked: messageStore.setEditModeOn(messageId)
onHoveredChanged: buttonsContainer.hoverChanged(editButton.hovered)
}
}
Loader {
active: buttonsContainer.pinButtonActive
sourceComponent: StatusFlatRoundButton {
id: pinButton
width: 32
height: 32
icon.name: buttonsContainer.pinnedMessage ? "unpin" : "pin"
type: StatusFlatRoundButton.Type.Tertiary
tooltip.text: buttonsContainer.pinnedMessage ? qsTr("Unpin") : qsTr("Pin")
onHoveredChanged: buttonsContainer.hoverChanged(pinButton.hovered)
onClicked: {
if (buttonsContainer.pinnedMessage) {
messageStore.unpinMessage(messageId)
return;
}
if (buttonsContainer.canPin) {
messageStore.pinMessage(messageId)
return;
}
if (!chatContentModule) {
console.warn("error on open pinned messages limit reached from message context menu - chat content module is not set")
return;
}
Global.openPopup(pinnedMessagesPopupComponent, {
store: rootStore,
messageStore: messageStore,
pinnedMessagesModel: chatContentModule.pinnedMessagesModel,
messageToPin: buttonsContainer.messageId
});
}
}
}
Loader {
active: buttonsContainer.deleteButtonActive && !buttonsContainer.isInPinnedPopup
sourceComponent: StatusFlatRoundButton {
id: deleteButton
objectName: "chatDeleteMessageButton"
width: 32
height: 32
type: StatusFlatRoundButton.Type.Tertiary
icon.name: "delete"
tooltip.text: qsTr("Delete")
onHoveredChanged: buttonsContainer.hoverChanged(deleteButton.hovered)
onClicked: {
if (!localAccountSensitiveSettings.showDeleteMessageWarning) {
messageStore.deleteMessage(messageId)
}
else {
Global.openPopup(deleteMessageConfirmationDialogComponent)
}
}
}
}
Component {
id: deleteMessageConfirmationDialogComponent
ConfirmationDialog {
confirmButtonObjectName: "chatButtonsPanelConfirmDeleteMessageButton"
header.title: qsTr("Confirm deleting this message")
confirmationText: qsTr("Are you sure you want to delete this message? Be aware that other clients are not guaranteed to delete the message as well.")
height: 260
checkbox.visible: true
executeConfirm: function () {
if (checkbox.checked) {
localAccountSensitiveSettings.showDeleteMessageWarning = false
}
close()
messageStore.deleteMessage(messageId)
}
onClosed: {
destroy()
}
}
}
}
}

View File

@ -1,192 +0,0 @@
import QtQuick 2.14
import QtQuick.Shapes 1.13
import QtGraphicalEffects 1.13
import utils 1.0
import shared.controls 1.0
import shared 1.0
import shared.panels 1.0
import shared.status 1.0
import shared.controls.chat 1.0
import StatusQ.Core.Utils 0.1 as StatusQUtils
Loader {
id: root
property bool amISenderOfTheRepliedMessage
property int repliedMessageContentType
property string repliedMessageSenderIcon
property bool repliedMessageIsEdited
property string repliedMessageSender
property string repliedMessageSenderPubkey
property bool repliedMessageSenderIsAdded
property string repliedMessageContent
property string repliedMessageImage
property bool isCurrentUser: false
property int nameMargin: 6
property int textFieldWidth: item ? item.textField.width : 0
property int textFieldImplicitWidth: 0
property int authorWidth: item ? item.authorMetrics.width : 0
property bool longReply: false
property color elementsColor: amISenderOfTheRepliedMessage ? Style.current.chatReplyCurrentUser : Style.current.secondaryText
property var container
property int chatHorizontalPadding
property string stickerData
signal clickMessage(bool isProfileClick, bool isSticker, bool isImage, var image, bool isEmoji, bool hideEmojiPicker, bool isReply)
signal scrollToBottom(bool isit, var container)
sourceComponent: Component {
Item {
property alias textField: lblReplyMessage
property alias authorMetrics: txtAuthorMetrics
id: chatReply
// childrenRect.height shows a binding loop for some reason, so we use heights instead
height: {
const h = userImage.height + 4
if (repliedMessageContentType === Constants.messageContentType.imageType) {
return h + imgReplyImage.height
}
if (repliedMessageContentType === Constants.messageContentType.stickerType) {
return h + stickerLoader.height
}
return h + lblReplyMessage.height
}
width: parent.width
clip: true
TextMetrics {
id: txtAuthorMetrics
font: lblReplyAuthor.font
text: lblReplyAuthor.text
}
Shape {
id: replyCorner
anchors.left: parent.left
anchors.leftMargin: 20 - 1
anchors.top: parent.top
anchors.topMargin: Style.current.smallPadding
width: 20
height: parent.height - anchors.topMargin
asynchronous: true
antialiasing: true
ShapePath {
id: capTest
strokeColor: Utils.setColorAlpha(root.elementsColor, 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: chatReply.height - replyCorner.anchors.topMargin }
}
}
UserImage {
id: userImage
anchors.left: replyCorner.right
anchors.leftMargin: Style.current.halfPadding
imageHeight: 20
imageWidth: 20
active: true
name: repliedMessageSender
pubkey: repliedMessageSenderPubkey
image: repliedMessageSenderIcon
onClicked: root.clickMessage(true, false, false, null, false, false, true)
}
StyledTextEdit {
id: lblReplyAuthor
text: repliedMessageSender
color: root.elementsColor
readOnly: true
font.pixelSize: Style.current.secondaryTextFontSize
selectByMouse: true
font.weight: Font.Medium
anchors.verticalCenter: userImage.verticalCenter
anchors.left: userImage.right
anchors.leftMargin: 5
}
StatusChatImage {
id: imgReplyImage
visible: repliedMessageContentType === Constants.messageContentType.imageType
imageWidth: 50
imageSource: repliedMessageImage
anchors.top: lblReplyAuthor.bottom
anchors.topMargin: nameMargin
anchors.left: userImage.left
chatHorizontalPadding: 0
container: root.container
allCornersRounded: true
playing: false
}
Loader {
id: stickerLoader
active: repliedMessageContentType === Constants.messageContentType.stickerType
anchors.top: lblReplyAuthor.bottom
anchors.topMargin: nameMargin
anchors.left: userImage.left
sourceComponent: Component {
StatusSticker {
id: stickerId
imageHeight: 56
imageWidth: 56
stickerData: root.stickerData
contentType: repliedMessageContentType
onLoaded: {
scrollToBottom(true, root.container)
}
}
}
}
StyledTextEdit {
id: lblReplyMessage
visible: repliedMessageContentType !== Constants.messageContentType.imageType && repliedMessageContentType !== Constants.messageContentType.stickerType
Component.onCompleted: textFieldImplicitWidth = implicitWidth
anchors.top: lblReplyAuthor.bottom
anchors.topMargin: nameMargin
text: {
if (repliedMessageIsEdited){
let index = repliedMessageContent.length - 4
return Utils.getReplyMessageStyle(StatusQUtils.Emoji.parse(Utils.linkifyAndXSS(repliedMessageContent.slice(0, index) + Constants.editLabel + repliedMessageContent.slice(index)), StatusQUtils.Emoji.size.small), amISenderOfTheRepliedMessage)
} else {
return Utils.getReplyMessageStyle(StatusQUtils.Emoji.parse(Utils.linkifyAndXSS(repliedMessageContent), StatusQUtils.Emoji.size.small), amISenderOfTheRepliedMessage)
}
}
textFormat: Text.RichText
color: root.elementsColor
readOnly: true
selectByMouse: true
font.pixelSize: Style.current.additionalTextSize
font.weight: Font.Medium
anchors.left: userImage.left
width: root.longReply ? parent.width : implicitWidth
height: 20
clip: true
z: 51
}
}
}
}

View File

@ -1,192 +0,0 @@
import QtQuick 2.3
import QtQuick.Controls 2.13
import QtGraphicalEffects 1.13
import shared 1.0
import shared.panels 1.0
import StatusQ.Controls 0.1 as StatusQ
import utils 1.0
Item {
id: root
height: 20
width: childrenRect.width
property int imageMargin: 4
signal addEmojiClicked()
signal hoverChanged(bool hovered)
signal toggleReaction(int emojiID)
signal setMessageActive(string messageId, bool active)
property var store
property bool isCurrentUser
property var emojiReactionsModel
property bool isMessageActive
Row {
spacing: root.imageMargin
Repeater {
id: reactionRepeater
width: childrenRect.width
model: root.emojiReactionsModel
Rectangle {
property bool isHovered: false
id: emojiContainer
width: emojiImage.width + emojiCount.width + (root.imageMargin * 2) + + 8
height: 20
radius: 10
color: model.didIReactWithThisEmoji ?
(isHovered ? Style.current.emojiReactionActiveBackgroundHovered : Style.current.secondaryBackground) :
(isHovered ? Style.current.emojiReactionBackgroundHovered : Style.current.emojiReactionBackground)
StatusQ.StatusToolTip {
visible: mouseArea.containsMouse
maxWidth: 400
text: root.store.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: Style.current.primary
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
}
}
}
}
SVGImage {
id: emojiImage
width: 15
height: 15
fillMode: Image.PreserveAspectFit
source: {
switch (model.emojiId) {
case 1: return Style.svg("emojiReactions/heart")
case 2: return Style.svg("emojiReactions/thumbsUp")
case 3: return Style.svg("emojiReactions/thumbsDown")
case 4: return Style.svg("emojiReactions/laughing")
case 5: return Style.svg("emojiReactions/sad")
case 6: return Style.svg("emojiReactions/angry")
default: return ""
}
}
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: root.imageMargin
}
StyledText {
id: emojiCount
text: model.numberOfReactions
anchors.verticalCenter: parent.verticalCenter
anchors.left: emojiImage.right
anchors.leftMargin: root.imageMargin
font.pixelSize: 12
color: model.didIReactWithThisEmoji ? Style.current.textColorTertiary : Style.current.textColor
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
root.hoverChanged(true)
emojiContainer.isHovered = true
}
onExited: {
root.hoverChanged(false)
emojiContainer.isHovered = false
}
onClicked: {
toggleReaction(model.emojiId)
}
}
}
}
Item {
width: addEmojiBtn.width + addEmojiBtn.anchors.leftMargin // there is more margin between the button and the emojis than between each emoji
height: addEmojiBtn.height
SVGImage {
property bool isHovered: false
id: addEmojiBtn
source: Style.svg("emoji")
width: 16.5
height: 16.5
anchors.left: parent.left
anchors.leftMargin: 2.5
}
ColorOverlay {
anchors.fill: addEmojiBtn
antialiasing: true
source: addEmojiBtn
color: addEmojiBtn.isHovered ? Style.current.primary : Style.current.secondaryText
}
MouseArea {
anchors.fill: addEmojiBtn
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: addEmojiBtn.isHovered = true
onExited: addEmojiBtn.isHovered = false
onClicked: {
if (typeof root.isMessageActive !== "undefined") {
setMessageActive(messageId, true);
}
root.addEmojiClicked();
}
}
StatusQ.StatusToolTip {
visible: addEmojiBtn.isHovered
text: qsTr("Add reaction")
}
}
}
}

View File

@ -63,12 +63,14 @@ StatusModal {
id: verificationMessage
anchors.top: description.bottom
anchors.topMargin: Style.current.padding
width: parent.width
isMessage: true
shouldRepeatHeader: true
messageTimestamp: root.messageTimestamp
senderId: root.senderPublicKey
senderDisplayName: root.senderDisplayName
senderIcon: root.senderIcon
message: root.challengeText
messageText: root.challengeText
messageContentType: Constants.messageContentType.messageType
placeholderMessage: true
}
@ -93,12 +95,14 @@ StatusModal {
id: responseMessage
visible: !!root.responseText
anchors.top: verificationMessage.bottom
width: parent.width
isMessage: true
shouldRepeatHeader: true
messageTimestamp: root.responseTimestamp
senderId: root.senderPublicKey
senderDisplayName: userProfile.name
senderIcon: userProfile.icon
message: root.responseText
messageText: root.responseText
messageContentType: Constants.messageContentType.messageType
placeholderMessage: true
}

View File

@ -4,4 +4,3 @@ DelegateModelGeneralized 1.0 DelegateModelGeneralized.qml
LoadingAnimation 1.0 LoadingAnimation.qml
MacTrafficLights 1.0 MacTrafficLights.qml
NumberPolyFill 1.0 polyfill.number.toLocaleString.js
XSS 1.0 xss.js

File diff suppressed because it is too large Load Diff

View File

@ -10,10 +10,12 @@ import StatusQ.Core.Utils 0.1 as StatusQUtils
Rectangle {
id: root
height: (root.contentType === Constants.messageContentType.imageType) ?
replyToUsername.height + imageThumbnail.height + Style.current.padding :
(root.contentType === Constants.messageContentType.stickerType) ?
replyToUsername.height + stickerThumbnail.height + Style.current.padding : 50
implicitHeight: (root.contentType === Constants.messageContentType.imageType)
? replyToUsername.height + imageThumbnail.height + Style.current.padding
: (root.contentType === Constants.messageContentType.stickerType)
? replyToUsername.height + stickerThumbnail.height + Style.current.padding
: 50
color: Style.current.replyBackground
radius: 16
clip: true
@ -61,7 +63,7 @@ Rectangle {
StyledText {
id: replyText
text: Utils.getMessageWithStyle(StatusQUtils.Emoji.parse(Utils.linkifyAndXSS(message)), false)
text: StatusQUtils.Utils.getMessageWithStyle(StatusQUtils.Emoji.parse(StatusQUtils.Utils.linkifyAndXSS(message)), false)
anchors.fill: parent
elide: Text.ElideRight
font.pixelSize: 13

View File

@ -233,7 +233,7 @@ Popup {
RowLayout {
id: stickersRowLayout
width: scrollView.availableWidth
width: inputScrollView.availableWidth
spacing: Style.current.padding
Repeater {

View File

@ -13,6 +13,7 @@ import shared.panels 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
@ -228,13 +229,13 @@ Rectangle {
subTitle: {
let user = ""
if (isCurrentUser) {
user = root.profileStore.ensName !== "" ? root.profileStore.ensName : Utils.elideText(root.profileStore.pubkey, 5)
user = root.profileStore.ensName !== "" ? root.profileStore.ensName : StatusQUtils.Utils.elideText(root.profileStore.pubkey, 5)
} else if (userIsEnsVerified) {
user = userEnsName
}
if (user === ""){
user = Utils.elideText(userPublicKey, 5)
user = StatusQUtils.Utils.elideText(userPublicKey, 5)
}
return Constants.userLinkPrefix + user;
}
@ -370,9 +371,10 @@ Rectangle {
isMessage: true
shouldRepeatHeader: true
messageTimestamp: root.verificationRequestedAt
senderId: profileStore.pubkey
senderDisplayName: userProfile.name
senderIcon: userProfile.icon
message: root.verificationChallenge
messageText: root.verificationChallenge
messageContentType: Constants.messageContentType.messageType
placeholderMessage: true
}
@ -380,13 +382,14 @@ Rectangle {
MessageView {
id: responseMessage
visible: root.showVerificationPendingSection && !!root.verificationResponse
width: parent.width
Layout.fillWidth: true
isMessage: true
shouldRepeatHeader: true
messageTimestamp: root.verificationRepliedAt
senderId: root.userPublicKey
senderDisplayName: root.verificationResponseDisplayName
senderIcon: root.verificationResponseIcon
message: root.verificationResponse
messageText: root.verificationResponse
messageContentType: Constants.messageContentType.messageType
placeholderMessage: true
}

View File

@ -122,16 +122,16 @@ Item {
}
text: {
if (contentType === Constants.messageContentType.stickerType) return "";
let msg = Utils.linkifyAndXSS(message);
if (contentType === Constants.messageContentType.stickerType)
return "";
let msg = StatusQUtils.Utils.linkifyAndXSS(message);
if (isEmoji)
return StatusQUtils.Emoji.parse(msg, StatusQUtils.Emoji.size.middle, StatusQUtils.Emoji.format.png);
if (isEdited) {
let index = msg.endsWith("code>") ? msg.length : msg.length - 4
return Utils.getMessageWithStyle(StatusQUtils.Emoji.parse(msg.slice(0, index) + Constants.editLabel + msg.slice(index)), isCurrentUser, hoveredLink)
return StatusQUtils.Utils.getMessageWithStyle(StatusQUtils.Emoji.parse(msg.slice(0, index) + Constants.editLabel + msg.slice(index)), isCurrentUser, hoveredLink)
}
return Utils.getMessageWithStyle(StatusQUtils.Emoji.parse(msg), isCurrentUser, hoveredLink)
return StatusQUtils.Utils.getMessageWithStyle(StatusQUtils.Emoji.parse(msg), isCurrentUser, hoveredLink)
}
}

View File

@ -1,805 +0,0 @@
import QtQuick 2.13
import QtGraphicalEffects 1.13
import utils 1.0
import shared.panels 1.0
import shared.status 1.0
import shared.controls 1.0
import shared.panels.chat 1.0
import shared.views.chat 1.0
import shared.controls.chat 1.0
import StatusQ.Controls 0.1 as StatusQControls
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Components 0.1
Item {
id: root
property var store
property var messageStore
property var usersStore
property var contactsStore
property var chatLogView
property var emojiPopup
property var messageContextMenu
property var container
property int contentType
property bool isChatBlocked: false
property bool isActiveChannel: false
property int senderTrustStatus
property int chatHorizontalPadding: Style.current.halfPadding
property int chatVerticalPadding: 7
property bool headerRepeatCondition: (authorCurrentMsg !== authorPrevMsg ||
shouldRepeatHeader || dateGroupLbl.visible || chatReply.active)
property bool stickersLoaded: false
property string sticker
property int stickerPack
property bool isMessageActive: false
property bool amISender: false
property string senderIcon: ""
property bool isHovered: false
property bool isInPinnedPopup: false
property bool pinnedMessage: false
property bool canPin: false
property string communityId
property bool editModeOn: false
property string linkUrls: ""
property string message: ""
property int prevMessageIndex
property string prevMsgTimestamp
property string messageTimestamp
property var transactionParams
signal openStickerPackPopup(string stickerPackId)
signal addEmoji(bool isProfileClick, bool isSticker, bool isImage , var image, bool isEmoji, bool hideEmojiPicker)
signal clickMessage(bool isProfileClick, bool isSticker, bool isImage, var image, bool isEmoji, bool hideEmojiPicker, bool isReply, bool isRightClickOnImage, string imageSource)
signal replyClicked(string messageId, string author)
signal imageClicked(var image)
function setMessageActive(messageId, active) {
if (active) {
activeMessage = messageId;
} else if (activeMessage === messageId) {
activeMessage = "";
}
}
width: parent.width
height: messageContainer.height + messageContainer.anchors.topMargin
+ (dateGroupLbl.visible ? dateGroupLbl.height + dateGroupLbl.anchors.topMargin : 0)
Connections {
target: !!root.messageStore && root.messageStore.messageModule ?
root.messageStore.messageModule : null
enabled: !!root.messageStore && !!root.messageStore.messageModule && responseTo !== ""
onRefreshAMessageUserRespondedTo: {
if(msgId === messageId)
chatReply.resetOriginalMessage()
}
}
Timer {
id: ensureMessageFullyVisibleTimer
interval: 1
onTriggered: {
chatLogView.positionViewAtIndex(ListView.currentIndex, ListView.Contain)
}
}
MessageMouseArea {
enabled: !root.isChatBlocked && !placeholderMessage && !isImage
anchors.fill: messageContainer
acceptedButtons: activityCenterMessage ? Qt.LeftButton : Qt.RightButton
messageContextMenu: root.messageContextMenu
messageContextMenuParent: root
isHovered: root.isHovered
isMessageActive: root.isMessageActive
isActivityCenterMessage: activityCenterMessage
stickersLoaded: root.stickersLoaded
onClickMessage: {
root.clickMessage(isProfileClick, isSticker, isImage, null, isEmoji, false, false, false, "");
}
}
ChatButtonsPanel {
contentType: messageContentType
parentIsHovered: !editModeOn && isHovered
isChatBlocked: root.isChatBlocked
onHoverChanged: {
hovered && setHovered(messageId, hovered)
}
onSetMessageActive: {
root.setMessageActive(messageId, active)
}
anchors.right: parent.right
anchors.rightMargin: 20
anchors.top: messageContainer.top
// This is not exactly like the design because the hover becomes messed up with the buttons on top of another Message
anchors.topMargin: -Style.current.halfPadding
messageContextMenu: root.messageContextMenu
isInPinnedPopup: root.isInPinnedPopup
fromAuthor: senderId
editBtnActive: isText && !editModeOn && root.amISender
pinButtonActive: {
if (!root.messageStore)
return false
const chatType = root.messageStore.getChatType();
const amIChatAdmin = root.messageStore.amIChatAdmin();
const pinMessageAllowedForMembers = root.messageStore.pinMessageAllowedForMembers()
return chatType === Constants.chatType.oneToOne ||
chatType === Constants.chatType.privateGroupChat && amIChatAdmin ||
chatType === Constants.chatType.communityChat && (amIChatAdmin || pinMessageAllowedForMembers);
}
deleteButtonActive: {
if (!root.messageStore)
return false;
const isMyMessage = senderId !== "" && senderId === userProfile.pubKey;
const chatType = root.messageStore.getChatType();
return isMyMessage &&
(contentType === Constants.messageContentType.messageType ||
contentType === Constants.messageContentType.stickerType ||
contentType === Constants.messageContentType.emojiType ||
contentType === Constants.messageContentType.imageType ||
contentType === Constants.messageContentType.audioType);
}
pinnedMessage: root.pinnedMessage
canPin: root.canPin
activityCenterMsg: activityCenterMessage
placeholderMsg: placeholderMessage
onClickMessage: {
root.clickMessage(isProfileClick, isSticker, isImage, image, isEmoji, hideEmojiPicker, false, false, "");
}
onReplyClicked: {
root.replyClicked(messageId, author)
}
}
Connections {
enabled: isHovered || isMessageActive
target: typeof root.messageContextMenu !== "undefined" ? root.messageContextMenu : null
onOpened: root.setMessageActive(messageId, true)
onClosed: root.setMessageActive(messageId, false)
}
DateGroup {
id: dateGroupLbl
previousMessageIndex: root.prevMessageIndex
previousMessageTimestamp: root.prevMsgTimestamp
messageTimestamp: root.messageTimestamp
isActivityCenterMessage: activityCenterMessage
}
function startMessageFoundAnimation() {
messageFoundAnimation.start();
}
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: messageContainer
opacity: 0
visible: (opacity > 0.001)
color: Style.current.backgroundHoverLight
}
Rectangle {
id: messageContainer
property alias messageContent: messageContent
anchors.top: dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top
anchors.topMargin: dateGroupLbl.visible ? (activityCenterMessage ? 4 : Style.current.padding) : 0
height: childrenRect.height
+ (chatName.visible || emojiReactionLoader.active ? Style.current.halfPadding : 0)
+ (chatName.visible && emojiReactionLoader.active ? Style.current.padding : 0)
+ (!chatName.visible && chatImageContent.active ? 6 : 0)
+ (emojiReactionLoader.active ? emojiReactionLoader.height: 0)
+ (retry.visible && !chatTime.visible ? Style.current.smallPadding : 0)
+ (pinnedRectangleLoader.active ? Style.current.smallPadding : 0)
+ (editModeOn ? 25 : 0)
+ (!chatName.visible ? 6 : 0)
width: parent.width
color: {
if (editModeOn) {
return Style.current.backgroundHoverLight
}
if (activityCenterMessage) {
return read ? Style.current.transparent : Utils.setColorAlpha(Style.current.blue, 0.1)
}
if (placeholderMessage) {
return Style.current.transparent
}
if (pinnedMessage) {
return isHovered || isMessageActive ? Style.current.pinnedMessageBackgroundHovered : Style.current.pinnedMessageBackground
}
return isHovered || isMessageActive ? (hasMention ? Style.current.mentionMessageHoverColor : Style.current.backgroundHoverLight) :
(hasMention ? Style.current.mentionMessageColor : Style.current.transparent)
}
Loader {
id: pinnedRectangleLoader
active: !editModeOn && pinnedMessage
anchors.left: chatName.left
anchors.top: parent.top
anchors.topMargin: active ? Style.current.halfPadding : 0
sourceComponent: Component {
Rectangle {
id: pinnedRectangle
height: 24
width: childrenRect.width + Style.current.smallPadding
color: Style.current.pinnedRectangleBackground
radius: 12
SVGImage {
id: pinImage
source: Style.svg("pin")
anchors.left: parent.left
anchors.leftMargin: 3
width: 16
height: 16
anchors.verticalCenter: parent.verticalCenter
ColorOverlay {
anchors.fill: parent
source: parent
color: Style.current.pinnedMessageBorder
}
}
StyledText {
text: qsTr("Pinned by %1").arg(Utils.getContactDetailsAsJson(messagePinnedBy).displayName)
anchors.left: pinImage.right
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 13
}
}
}
}
// Not Refactored Yet
// Connections {
// enabled: !!rootStore
// target: enabled ? rootStore.chatsModelInst.messageView : null
// onMessageEdited: {
// if(chatReply.item)
// chatReply.item.messageEdited(editedMessageId, editedMessageContent)
// }
// }
ChatReplyPanel {
id: chatReply
anchors.top: pinnedRectangleLoader.active ? pinnedRectangleLoader.bottom : parent.top
anchors.topMargin: active ? 4 : 0
anchors.left: chatImage.left
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
isCurrentUser: root.amISender
longReply: active && textFieldImplicitWidth > width
container: root.container
chatHorizontalPadding: chatHorizontalPadding
active: responseTo !== "" && !activityCenterMessage
function resetOriginalMessage() {
if(!root.messageStore)
return
let obj = root.messageStore.getMessageByIdAsJson(responseTo)
if(!obj)
return
amISenderOfTheRepliedMessage = obj.amISender
repliedMessageContentType = obj.contentType
repliedMessageSenderIcon = obj.senderIcon
// TODO: not sure about is edited at the moment
repliedMessageIsEdited = false
repliedMessageSender = obj.senderDisplayName
repliedMessageSenderPubkey = obj.senderId
repliedMessageSenderIsAdded = obj.senderIsAdded
repliedMessageContent = obj.messageText
repliedMessageImage = obj.messageImage
stickerData = obj.sticker
}
Component.onCompleted: {
resetOriginalMessage()
}
onScrollToBottom: {
// Not Refactored Yet
// messageStore.scrollToBottom(isit, root.container);
}
onClickMessage: {
root.clickMessage(isProfileClick, isSticker, isImage, image, isEmoji, hideEmojiPicker, isReply, false, "")
}
}
UserImage {
id: chatImage
active: isMessage && headerRepeatCondition
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.top: chatReply.active ? chatReply.bottom :
pinnedRectangleLoader.active ? pinnedRectangleLoader.bottom : parent.top
anchors.topMargin: chatReply.active || pinnedRectangleLoader.active ? 4 : Style.current.smallPadding
image: root.senderIcon
pubkey: senderId
name: senderDisplayName
messageContextMenu: root.messageContextMenu
onClicked: root.clickMessage(true, false, false, null, false, false, false, false, "")
}
UsernameLabel {
id: chatName
visible: !editModeOn && isMessage && headerRepeatCondition
anchors.leftMargin: chatHorizontalPadding
anchors.top: chatImage.top
anchors.left: chatImage.right
messageContextMenu: root.messageContextMenu
displayName: senderDisplayName
localName: senderLocalName
amISender: root.amISender
onClickMessage: {
root.clickMessage(true, false, false, null, false, false, false, false, "")
}
}
VerificationLabel {
id: trustStatus
anchors.left: chatName.right
anchors.leftMargin: visible ? 4 : 0
anchors.bottom: chatName.bottom
anchors.bottomMargin: 4
visible: !root.amISender && chatName.visible
trustStatus: senderTrustStatus
}
ChatTimePanel {
id: chatTime
visible: !editModeOn && headerRepeatCondition
anchors.verticalCenter: chatName.verticalCenter
anchors.left: trustStatus.right
anchors.leftMargin: 4
color: Style.current.secondaryText
timestamp: root.messageTimestamp
}
Loader {
id: editMessageLoader
active: editModeOn
anchors.top: chatReply.active ? chatReply.bottom : parent.top
anchors.left: chatImage.right
anchors.leftMargin: chatHorizontalPadding
anchors.right: parent.right
anchors.rightMargin: chatHorizontalPadding
height: (item !== null && typeof(item)!== 'undefined')? item.height: 0
sourceComponent: Item {
id: editText
height: childrenRect.height
property bool suggestionsOpened: false
Keys.onEscapePressed: {
if (!suggestionsOpened) {
cancelBtn.clicked()
}
suggestionsOpened = false
}
StatusChatInput {
id: editTextInput
store: root.store
usersStore: root.usersStore
chatInputPlaceholder: qsTr("Pinned by %1")
chatType: messageStore.getChatType()
isEdit: true
emojiPopup: root.emojiPopup
messageContextMenu: root.messageContextMenu
onSendMessage: {
saveBtn.clicked(null)
}
suggestions.onVisibleChanged: {
if (suggestions.visible) {
editText.suggestionsOpened = true
}
}
Component.onCompleted: {
let mentionsMap = new Map()
let index = 0
while (true) {
index = message.indexOf("<a href=", index)
if (index < 0) {
break
}
let startIndex = index
let endIndex = message.indexOf("</a>", index) + 4
if (endIndex < 0) {
index += 8 // "<a href="
continue
}
let addrIndex = message.indexOf("0x", index + 8)
if (addrIndex < 0) {
index += 8 // "<a href="
continue
}
let addrEndIndex = message.indexOf("\"", addrIndex)
if (addrEndIndex < 0) {
index += 8 // "<a href="
continue
}
const mentionLink = message.substring(startIndex, endIndex)
const linkTag = message.substring(index, endIndex)
const linkText = linkTag.replace(/(<([^>]+)>)/ig,"").trim()
const atSymbol = linkText.startsWith("@") ? '' : '@'
const mentionTag = Constants.mentionSpanTag + atSymbol + linkText + '</span> '
mentionsMap.set(mentionLink, mentionTag)
index += linkTag.length
}
var text = message
for (let [key, value] of mentionsMap) {
text = text.replace(new RegExp(key, 'g'), value)
}
editTextInput.textInput.text = text
editTextInput.textInput.cursorPosition = editTextInput.textInput.length
}
}
StatusQControls.StatusFlatButton {
id: cancelBtn
anchors.left: parent.left
anchors.leftMargin: Style.current.halfPadding
anchors.top: editTextInput.bottom
text: qsTr("Cancel")
onClicked: {
messageStore.setEditModeOff(messageId)
editTextInput.textInput.text = StatusQUtils.Emoji.parse(message)
ensureMessageFullyVisibleTimer.start()
}
}
StatusQControls.StatusButton {
id: saveBtn
anchors.left: cancelBtn.right
anchors.leftMargin: Style.current.halfPadding
anchors.top: editTextInput.bottom
text: qsTr("Save")
enabled: editTextInput.textInput.text.trim().length > 0
onClicked: {
let msg = rootStore.plainText(StatusQUtils.Emoji.deparse(editTextInput.textInput.text))
if (msg.length > 0){
msg = messageStore.interpretMessage(msg)
messageStore.setEditModeOff(messageId)
messageStore.editMessage(messageId, msg)
}
}
}
}
}
Item {
id: messageContent
height: childrenRect.height + (isEmoji ? 2 : 0)
anchors.top: chatName.visible ? chatName.bottom :
chatReply.active ? chatReply.bottom :
pinnedRectangleLoader.active ? pinnedRectangleLoader.bottom : parent.top
anchors.left: parent.left
anchors.leftMargin: chatImage.imageWidth + Style.current.padding + root.chatHorizontalPadding
anchors.right: parent.right
anchors.rightMargin: root.chatHorizontalPadding
anchors.topMargin: (!chatName.visible || !chatReply.active || !pinnedRectangleLoader.active) ? 4 : 0
visible: !editModeOn
ChatTextView {
id: chatText
readonly property int leftPadding: chatImage.anchors.leftMargin + chatImage.width + chatHorizontalPadding
visible: isText || isEmoji || (isImage && root.message !== "<p>Update to latest version to see a nice image here!</p>")
message: Utils.removeGifUrls(root.message)
anchors.top: parent.top
anchors.topMargin: isEmoji ? 2 : 0
anchors.left: parent.left
anchors.right: parent.right
textField.rightPadding: Style.current.bigPadding
onLinkActivated: {
if (activityCenterMessage) {
root.clickMessage(false, isSticker, false, null, false, false, false, false, "")
}
}
}
Loader {
id: chatImageContent
active: isImage
anchors.top: chatText.visible ? chatText.bottom : parent.top
anchors.topMargin: active ? 6 : 0
z: 51
sourceComponent: Component {
StatusChatImage {
playing: root.messageStore.playAnimation
imageSource: messageImage
imageWidth: 200
isActiveChannel: root.isActiveChannel
onClicked: {
if (mouse.button === Qt.LeftButton) {
root.imageClicked(image)
}
else if (mouse.button === Qt.RightButton) {
// Set parent, X & Y positions for the messageContextMenu
root.messageContextMenu.parent = root
const mappedPos = root.mapFromItem(
this,
mouse.x + Style.current.smallPadding,
mouse.y - Style.current.smallPadding)
root.messageContextMenu.setXPosition = function() { return mappedPos.x }
root.messageContextMenu.setYPosition = function() { return mappedPos.y }
root.clickMessage(false, false, true, image, false, true, false, true, imageSource)
}
}
container: root.container
}
}
}
Loader {
id: stickerLoader
active: contentType === Constants.messageContentType.stickerType
anchors.top: parent.top
anchors.topMargin: active ? Style.current.halfPadding : 0
sourceComponent: Component {
Rectangle {
id: stickerContainer
color: Style.current.transparent
border.color: isHovered ? Qt.darker(Style.current.border, 1.1) : Style.current.border
border.width: 1
radius: 16
width: stickerId.width + 2 * chatVerticalPadding
height: stickerId.height + 2 * chatVerticalPadding
StatusSticker {
id: stickerId
anchors.top: parent.top
anchors.topMargin: chatVerticalPadding
anchors.left: parent.left
anchors.leftMargin: chatVerticalPadding
contentType: root.contentType
stickerData: root.sticker
onLoaded: {
if(!root.messageStore)
return
// Not refactored yet
// root.messageStore.scrollToBottom(true, root.container)
}
}
}
}
}
MessageMouseArea {
id: messageMouseArea
anchors.fill: stickerLoader.active ? stickerLoader : chatText
z: activityCenterMessage ? chatText.z + 1 : chatText.z -1
enabled: !root.isChatBlocked && !placeholderMessage
messageContextMenu: root.messageContextMenu
messageContextMenuParent: root
isHovered: root.isHovered
isMessageActive: root.isMessageActive
isActivityCenterMessage: activityCenterMessage
stickersLoaded: root.stickersLoaded
onClickMessage: {
root.clickMessage(isProfileClick, isSticker, isImage, null, false, false, false, false, "");
}
onOpenStickerPackPopup: {
root.openStickerPackPopup(root.stickerPack);
}
onSetMessageActive: {
root.setMessageActive(messageId, active);
}
}
Loader {
id: linksLoader
active: !!linkUrls
height: item ? item.height : 0
anchors.top: chatText.bottom
anchors.topMargin: active ? Style.current.halfPadding : 0
sourceComponent: Component {
LinksMessageView {
linkUrls: root.linkUrls
container: root.container
messageStore: root.messageStore
store: root.store
isCurrentUser: root.amISender
}
}
}
Loader {
id: audioPlayerLoader
active: isAudio
anchors.top: parent.top
anchors.topMargin: active ? Style.current.halfPadding : 0
sourceComponent: Component {
AudioPlayerPanel {
audioSource: audio
}
}
}
Loader {
id: transactionBubbleLoader
active: contentType === Constants.messageContentType.transactionType
anchors.top: parent.top
anchors.topMargin: active ? (chatName.visible ? 4 : 6) : 0
sourceComponent: Component {
TransactionBubbleView {
transactionParams: root.transactionParams
store: root.store
contactsStore: root.contactsStore
}
}
}
Loader {
active: contentType === Constants.messageContentType.communityInviteType
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: active ? 8 : 0
sourceComponent: Component {
id: invitationBubble
InvitationBubbleView {
store: root.store
communityId: root.communityId
}
}
}
}
Retry {
id: retry
height: visible ? implicitHeight : 0
anchors.left: chatTime.visible ? chatTime.right : messageContent.left
anchors.leftMargin: chatTime.visible ? chatHorizontalPadding : 0
anchors.top: chatTime.visible ? chatTime.top : messageContent.bottom
anchors.topMargin: chatTime.visible ? 0 : -4
anchors.bottom: chatTime.visible ? chatTime.bottom : undefined
isCurrentUser: root.amISender
isExpired: isExpired
timeout: timeout
onClicked: {
// Not Refactored Yet
// rootStore.chatsModelInst.messageView.resendMessage(chatId, messageId)
}
}
}
Loader {
active: !activityCenterMessage && (hasMention || pinnedMessage)
height: messageContainer.height
anchors.left: messageContainer.left
anchors.top: messageContainer.top
sourceComponent: Component {
Rectangle {
id: mentionBorder
color: pinnedMessage ? Style.current.pinnedMessageBorder : Style.current.mentionColor
width: 2
height: parent.height
}
}
}
HoverHandler {
enabled: !activityCenterMessage && !chatLogView.flickingVertically &&
(forceHoverHandler || (typeof root.messageContextMenu !== "undefined" && typeof Global.profilePopupOpened !== "undefined" &&
!root.messageContextMenu.opened && !Global.profilePopupOpened && !Global.popupOpened))
onHoveredChanged: {
setHovered(messageId, hovered);
}
}
Loader {
id: emojiReactionLoader
active: reactionsModel.count > 0
anchors.bottom: messageContainer.bottom
anchors.bottomMargin: Style.current.halfPadding
anchors.left: messageContainer.left
anchors.leftMargin: messageContainer.messageContent.anchors.leftMargin
sourceComponent: Component {
EmojiReactionsPanel {
id: emojiRect
store: root.messageStore
emojiReactionsModel: reactionsModel
onHoverChanged: {
setHovered(messageId, hovered)
}
isMessageActive: isMessageActive
isCurrentUser: root.amISender
onAddEmojiClicked: {
if(root.isChatBlocked)
return
// First set parent, X & Y positions for the messageContextMenu
root.messageContextMenu.parent = emojiRect
root.messageContextMenu.setXPosition = function() { return (root.messageContextMenu.parent.x + root.messageContextMenu.parent.width + 4) }
root.messageContextMenu.setYPosition = function() { return (-root.messageContextMenu.height - 4) }
// Second, add emoji that also triggers setXYPosition methods / open popup:
root.addEmoji(false, false, false, null, true, false);
}
onToggleReaction: {
if(root.isChatBlocked)
return
if(!root.messageStore)
{
console.error("reaction cannot be toggled, message store is not valid")
return
}
root.messageStore.toggleReaction(messageId, emojiID)
}
onSetMessageActive: {
root.setMessageActive(messageId, active);
}
}
}
}
}

View File

@ -102,9 +102,6 @@ StatusPopupMenu {
readonly property bool userTrustIsUnknown: d.contactDetails && d.contactDetails.trustStatus === Constants.trustStatus.unknown
readonly property bool userIsUntrustworthy: d.contactDetails && d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy
property var setXPosition: function() {return 0}
property var setYPosition: function() {return 0}
property var emojiReactionsReactedByUser: []
signal openProfileClicked(string publicKey, string state)
@ -142,14 +139,6 @@ StatusPopupMenu {
d.contactDetails = {}
}
onHeightChanged: { root.y = setYPosition(); }
onWidthChanged: { root.x = setXPosition(); }
onOpened: {
// Trigger x and y position:
x = setXPosition()
y = setYPosition()
}
width: Math.max(emojiContainer.visible ? emojiContainer.width : 0,
(root.isRightClickOnImage && !root.pinnedPopup) ? 176 : 230)
@ -202,7 +191,8 @@ StatusPopupMenu {
displayName: root.selectedUserDisplayName
pubkey: root.selectedUserPublicKey
icon: root.selectedUserIcon
trustStatus: d.contactDetails.trustStatus
trustStatus: d.contactDetails && d.contactDetails.trustStatus ? d.contactDetails.trustStatus
: Constants.trustStatus.unknown
isContact: root.isMyMutualContact
isCurrentUser: root.isMe
}

View File

@ -1,38 +1,24 @@
import QtQuick 2.13
import StatusQ.Components 0.1
import utils 1.0
import shared.panels 1.0
import shared.status 1.0
import shared.controls 1.0
import shared.popups 1.0
import shared.panels.chat 1.0
import shared.views.chat 1.0
import shared.controls.chat 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
Loader {
id: root
width: parent.width
z: (typeof chatLogView === "undefined") ? 1 : (chatLogView.count - index)
sourceComponent: {
switch(contentType) {
case Constants.messageContentType.chatIdentifier:
return channelIdentifierComponent
case Constants.messageContentType.fetchMoreMessagesButton:
return fetchMoreMessagesButtonComponent
case Constants.messageContentType.systemMessagePrivateGroupType:
return privateGroupHeaderComponent
case Constants.messageContentType.gapType:
return gapComponent
default:
return compactMessageComponent
}
}
property var store
property var rootStore
property var messageStore
property var usersStore
property var contactsStore
@ -54,30 +40,37 @@ Loader {
property string senderId: ""
property string senderDisplayName: ""
property string senderLocalName: ""
property string senderEnsName: ""
property string senderIcon: ""
property bool amISender: false
property bool senderIsAdded: false
property int senderTrustStatus: Constants.trustStatus.unknown
readonly property string senderIconToShow: {
if ((!senderIsAdded &&
Global.privacyModuleInst.profilePicturesVisibility !==
Constants.profilePicturesVisibility.everyone)) {
Global.privacyModuleInst.profilePicturesVisibility !==
Constants.profilePicturesVisibility.everyone)) {
return ""
}
return senderIcon
}
property string message: ""
property string messageText: ""
property string messageImage: ""
property string messageTimestamp: ""
property double messageTimestamp: 0 // We use double, because QML's int is too small
property string messageOutgoingStatus: ""
property int messageContentType: 1
property bool pinnedMessage: false
property string messagePinnedBy: ""
property var reactionsModel: []
property string linkUrls: ""
property bool isInPinnedPopup: false // The pinned popup limits the number of buttons shown
property var transactionParams
// External behavior changers
property bool isInPinnedPopup: false // The pinned popup limits the number of buttons shown
property bool disableHover: false // Used to force the HoverHandler to be active (useful for messages in popups)
property bool placeholderMessage: false
property bool activityCenterMessage: false
property bool activityCenterMessageRead: true
property int gapFrom: 0
property int gapTo: 0
@ -86,94 +79,63 @@ Loader {
property int nextMessageIndex: -1
property var nextMessageAsJsonObj
property string hoveredMessage
property string activeMessage
property bool isHovered: typeof hoveredMessage !== "undefined" && hoveredMessage === messageId
property bool isMessageActive: typeof activeMessage !== "undefined" && activeMessage === messageId
property bool editModeOn: false
property bool isEdited: false
function setHovered(messageId, hovered) {
if (hovered) {
hoveredMessage = messageId;
} else if (hoveredMessage === messageId) {
hoveredMessage = "";
}
}
property string responseTo: responseToMessageWithId
// Legacy
property string responseTo: responseToMessageWithId
property bool isCurrentUser: amISender
property int contentType: messageContentType
property string timestamp: messageTimestamp
property string displayUserName: senderDisplayName
property string outgoingStatus: messageOutgoingStatus
property string authorCurrentMsg: senderId
property string authorPrevMsg: {
if(!prevMessageAsJsonObj ||
// The system message for private groups appear as created by the group host, but it shouldn't
prevMessageAsJsonObj.contentType === Constants.messageContentType.systemMessagePrivateGroupType) {
// The system message for private groups appear as created by the group host, but it shouldn't
prevMessageAsJsonObj.contentType === Constants.messageContentType.systemMessagePrivateGroupType) {
return ""
}
return prevMessageAsJsonObj.senderId
}
property string prevMsgTimestamp: {
if(!prevMessageAsJsonObj)
return ""
property double prevMsgTimestamp: prevMessageAsJsonObj ? prevMessageAsJsonObj.timestamp : 0
property double nextMsgTimestamp: nextMessageAsJsonObj ? nextMessageAsJsonObj.timestamp : 0
return prevMessageAsJsonObj.timestamp
}
property string nextMsgTimestamp: {
if(!nextMessageAsJsonObj)
return ""
property bool shouldRepeatHeader: ((messageTimestamp - prevMsgTimestamp) / 60 / 1000) > Constants.repeatHeaderInterval
return nextMessageAsJsonObj.timestamp
}
property bool shouldRepeatHeader: ((parseInt(timestamp, 10) - parseInt(prevMsgTimestamp, 10)) / 60 / 1000) > Constants.repeatHeaderInterval
//////////////////////////////////////
//TODO CHECCK - REMOVE
property string plainText: "That's right. We're friends... Of justice, that is."
property string emojiReactions: ""
property bool timeout: false
property bool hasMention: false
property bool placeholderMessage: false
property bool activityCenterMessage: false
property bool read: true
property bool forceHoverHandler: false // Used to force the HoverHandler to be active (useful for messages in popups)
property string replaces: ""
property bool isEdited: false
property bool stickersLoaded: false
//////////////////////////////////////
property string sticker: "Qme8vJtyrEHxABcSVGPF95PtozDgUyfr1xGjePmFdZgk9v"
property int stickerPack: -1
property bool isEmoji: contentType === Constants.messageContentType.emojiType
property bool isImage: contentType === Constants.messageContentType.imageType
property bool isAudio: contentType === Constants.messageContentType.audioType
property bool isStatusMessage: contentType === Constants.messageContentType.systemMessagePrivateGroupType
property bool isSticker: contentType === Constants.messageContentType.stickerType
property bool isText: contentType === Constants.messageContentType.messageType || contentType === Constants.messageContentType.editType
property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio
|| contentType === Constants.messageContentType.communityInviteType || contentType === Constants.messageContentType.transactionType
property bool isExpired: (outgoingStatus === "sending" && (Math.floor(timestamp) + 180000) < Date.now())
property bool isEmoji: messageContentType === Constants.messageContentType.emojiType
property bool isImage: messageContentType === Constants.messageContentType.imageType
property bool isAudio: messageContentType === Constants.messageContentType.audioType
property bool isStatusMessage: messageContentType === Constants.messageContentType.systemMessagePrivateGroupType
property bool isSticker: messageContentType === Constants.messageContentType.stickerType
property bool isText: messageContentType === Constants.messageContentType.messageType || messageContentType === Constants.messageContentType.editType
property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio
|| messageContentType === Constants.messageContentType.communityInviteType || messageContentType === Constants.messageContentType.transactionType
property bool isExpired: (outgoingStatus === "sending" && (Math.floor(messageTimestamp) + 180000) < Date.now())
property int statusAgeEpoch: 0
signal imageClicked(var image)
property var scrollToBottom: function () {}
property var clickMessage: function(isProfileClick,
isSticker = false,
isImage = false,
image = null,
isEmoji = false,
hideEmojiPicker = false,
isReply = false,
isRightClickOnImage = false,
imageSource = "") {
// WARNING: To much arguments here. Create an object argument.
property var messageClickHandler: function(sender, point,
isProfileClick,
isSticker = false,
isImage = false,
image = null,
isEmoji = false,
hideEmojiPicker = false,
isReply = false,
isRightClickOnImage = false,
imageSource = "") {
if (placeholderMessage || activityCenterMessage) {
return
@ -188,7 +150,7 @@ Loader {
messageContextMenu.messageSenderId = root.senderId
messageContextMenu.messageContentType = root.messageContentType
messageContextMenu.pinnedMessage = root.pinnedMessage
messageContextMenu.canPin = messageStore.getNumberOfPinnedMessages() < Constants.maxNumberOfPins
messageContextMenu.canPin = d.canPin
messageContextMenu.selectedUserPublicKey = root.senderId
messageContextMenu.selectedUserDisplayName = root.senderDisplayName
@ -202,7 +164,7 @@ Loader {
messageContextMenu.isSticker = isSticker
messageContextMenu.hideEmojiPicker = hideEmojiPicker
if(isReply){
if (isReply){
let obj = messageStore.getMessageByIdAsJson(responseTo)
if(!obj)
return
@ -213,15 +175,16 @@ Loader {
messageContextMenu.selectedUserIcon = obj.senderIcon
}
messageContextMenu.popup()
messageContextMenu.parent = sender;
messageContextMenu.popup(point);
}
signal showReplyArea(string messageId, string author)
// function showReactionAuthors(fromAccounts, emojiId) {
// return root.rootStore.showReactionAuthors(fromAccounts, emojiId)
// }
// function showReactionAuthors(fromAccounts, emojiId) {
// return root.rootStore.showReactionAuthors(fromAccounts, emojiId)
// }
function startMessageFoundAnimation() {
root.item.startMessageFoundAnimation();
@ -231,23 +194,76 @@ Loader {
signal openStickerPackPopup(string stickerPackId)
// Not Refactored Yet
// Connections {
// enabled: (!placeholderMessage && !!root.rootStore)
// target: !!root.rootStore ? root.rootStore.allContacts : null
// onContactChanged: {
// if (pubkey === fromAuthor) {
// const img = appMain.getProfileImage(userPubKey, isCurrentUser, useLargeImage)
// if (img) {
// profileImageSource = img
// }
// } else if (replyMessageIndex > -1 && pubkey === repliedMessageAuthorPubkey) {
// const imgReply = appMain.getProfileImage(repliedMessageAuthorPubkey, repliedMessageAuthorIsCurrentUser, false)
// if (imgReply) {
// repliedMessageUserImage = imgReply
// }
// }
// }
// }
// Connections {
// enabled: (!placeholderMessage && !!root.rootStore)
// target: !!root.rootStore ? root.rootStore.allContacts : null
// onContactChanged: {
// if (pubkey === fromAuthor) {
// const img = appMain.getProfileImage(userPubKey, isCurrentUser, useLargeImage)
// if (img) {
// profileImageSource = img
// }
// } else if (replyMessageIndex > -1 && pubkey === repliedMessageAuthorPubkey) {
// const imgReply = appMain.getProfileImage(repliedMessageAuthorPubkey, repliedMessageAuthorIsCurrentUser, false)
// if (imgReply) {
// repliedMessageUserImage = imgReply
// }
// }
// }
// }
z: (typeof chatLogView === "undefined") ? 1 : (chatLogView.count - index)
sourceComponent: {
switch(messageContentType) {
case Constants.messageContentType.chatIdentifier:
return channelIdentifierComponent
case Constants.messageContentType.fetchMoreMessagesButton:
return fetchMoreMessagesButtonComponent
case Constants.messageContentType.systemMessagePrivateGroupType:
return privateGroupHeaderComponent
case Constants.messageContentType.gapType:
return gapComponent
default:
return messageComponent
}
}
QtObject {
id: d
readonly property bool canPin: !!messageStore &&
messageStore.getNumberOfPinnedMessages() < Constants.maxNumberOfPins
readonly property int chatButtonSize: 32
property string activeMessage
readonly property bool isMessageActive: typeof activeMessage !== "undefined" && activeMessage === messageId
function setMessageActive(messageId, active) {
// TODO: Is argument messageId actually needed?
// It was probably used with dynamic scoping,
// but not this method can be moved to private `d`.
// Probably that it was done this way, because `MessageView` is reused as delegate.
if (active) {
d.activeMessage = messageId;
return;
}
if (d.activeMessage === messageId) {
d.activeMessage = "";
return;
}
}
}
Connections {
enabled: d.isMessageActive
target: root.messageContextMenu
onClosed: {
d.setMessageActive(root.messageId, false)
}
}
Component {
id: gapComponent
@ -293,18 +309,18 @@ Loader {
wrapMode: Text.Wrap
text: {
return `<html>`+
`<head>`+
`<style type="text/css">`+
`a {`+
`<head>`+
`<style type="text/css">`+
`a {`+
`color: ${Style.current.textColor};`+
`text-decoration: none;`+
`}`+
`</style>`+
`</head>`+
`<body>`+
`${message}`+
`</body>`+
`</html>`;
`}`+
`</style>`+
`</head>`+
`<body>`+
`${messageText}`+
`</body>`+
`</html>`;
}
visible: isStatusMessage
font.pixelSize: 14
@ -318,60 +334,465 @@ Loader {
}
Component {
id: compactMessageComponent
id: messageComponent
CompactMessageView {
container: root
store: root.store
message: root.message
messageStore: root.messageStore
usersStore: root.usersStore
contactsStore: root.contactsStore
messageContextMenu: root.messageContextMenu
contentType: root.messageContentType
isChatBlocked: root.isChatBlocked
isActiveChannel: root.isActiveChannel
emojiPopup: root.emojiPopup
senderTrustStatus: root.senderTrustStatus
chatLogView: root.chatLogView
StatusMessage {
id: delegate
prevMessageIndex: root.prevMessageIndex
prevMsgTimestamp: root.prevMsgTimestamp
messageTimestamp: root.messageTimestamp
communityId: root.communityId
stickersLoaded: root.stickersLoaded
sticker: root.sticker
stickerPack: root.stickerPack
isMessageActive: root.isMessageActive
senderIcon: root.senderIconToShow
amISender: root.amISender
isHovered: root.isHovered
editModeOn: root.editModeOn
linkUrls: root.linkUrls
isInPinnedPopup: root.isInPinnedPopup
pinnedMessage: root.pinnedMessage
canPin: !!messageStore && messageStore.getNumberOfPinnedMessages() < Constants.maxNumberOfPins
transactionParams: root.transactionParams
onAddEmoji: {
root.clickMessage(isProfileClick, isSticker, isImage , image, isEmoji, hideEmojiPicker)
function convertContentType(value) {
switch (value) {
case Constants.messageContentType.messageType:
return StatusMessage.ContentType.Text;
case Constants.messageContentType.stickerType:
return StatusMessage.ContentType.Sticker;
case Constants.messageContentType.emojiType:
return StatusMessage.ContentType.Emoji;
case Constants.messageContentType.transactionType:
return StatusMessage.ContentType.Transaction;
case Constants.messageContentType.imageType:
return StatusMessage.ContentType.Image;
case Constants.messageContentType.audioType:
return StatusMessage.ContentType.Audio;
case Constants.messageContentType.communityInviteType:
return StatusMessage.ContentType.Invitation;
case Constants.messageContentType.fetchMoreMessagesButton:
case Constants.messageContentType.chatIdentifier:
case Constants.messageContentType.unknownContentType:
case Constants.messageContentType.statusType:
case Constants.messageContentType.systemMessagePrivateGroupType:
case Constants.messageContentType.gapType:
case Constants.messageContentType.editType:
default:
return StatusMessage.ContentType.Unknown;
}
}
onClickMessage: {
root.clickMessage(isProfileClick, isSticker, isImage, image, isEmoji, hideEmojiPicker, isReply, isRightClickOnImage, imageSource)
readonly property int contentType: convertContentType(root.messageContentType)
readonly property bool isReply: root.responseTo !== ""
readonly property var replyMessage: root.messageStore && isReply ? root.messageStore.getMessageByIdAsJson(root.responseTo) : null
readonly property string replySenderId: replyMessage ? replyMessage.senderId : ""
function editCompletedHandler(newMessageText) {
const message = root.rootStore.plainText(StatusQUtils.Emoji.deparse(newMessageText))
if (message.length <= 0)
return;
const interpretedMessage = root.messageStore.interpretMessage(message)
root.messageStore.setEditModeOff(root.messageId)
root.messageStore.editMessage(root.messageId, interpretedMessage)
}
onOpenStickerPackPopup: {
root.openStickerPackPopup(stickerPackId);
audioMessageInfoText: qsTr("Audio Message")
cancelButtonText: qsTr("Cancel")
saveButtonText: qsTr("Save")
loadingImageText: qsTr("Loading image...")
errorLoadingImageText: qsTr("Error loading the image")
resendText: qsTr("Resend")
pinnedMsgInfoText: qsTr("Pinned by")
reactionIcons: [
Style.svg("emojiReactions/heart"),
Style.svg("emojiReactions/thumbsUp"),
Style.svg("emojiReactions/thumbsDown"),
Style.svg("emojiReactions/laughing"),
Style.svg("emojiReactions/sad"),
Style.svg("emojiReactions/angry"),
]
timestamp: root.messageTimestamp
editMode: root.editModeOn
isAReply: delegate.isReply
isEdited: root.isEdited
hasMention: root.hasMention
isPinned: root.pinnedMessage
pinnedBy: root.pinnedMessage ? Utils.getContactDetailsAsJson(root.messagePinnedBy).displayName : ""
hasExpired: root.isExpired
reactionsModel: root.reactionsModel
previousMessageIndex: root.prevMessageIndex
previousMessageTimestamp: root.prevMsgTimestamp
showHeader: root.authorCurrentMsg !== root.authorPrevMsg ||
root.shouldRepeatHeader || dateGroupVisible || isAReply
isActiveMessage: d.isMessageActive
disableHover: root.disableHover ||
(root.chatLogView && root.chatLogView.flickingVertically) ||
activityCenterMessage ||
root.messageContextMenu.opened ||
!!Global.profilePopupOpened ||
!!Global.popupOpened
hideQuickActions: root.isChatBlocked ||
root.placeholderMessage ||
root.activityCenterMessage
overrideBackground: root.activityCenterMessage || root.placeholderMessage
overrideBackgroundColor: {
if (root.activityCenterMessage && root.activityCenterMessageRead)
return Utils.setColorAlpha(Style.current.blue, 0.1);
return "transparent";
}
onReplyClicked: {
root.showReplyArea(messageId, author)
timestampString: Utils.formatShortTime(timestamp,
localAccountSensitiveSettings.is24hTimeFormat)
timestampTooltipString: Utils.formatLongDateTime(timestamp,
localAccountSensitiveSettings.isDDMMYYDateFormat,
localAccountSensitiveSettings.is24hTimeFormat);
onEditCancelled: {
root.messageStore.setEditModeOff(root.messageId)
}
onImageClicked: root.imageClicked(image)
onEditCompleted: {
delegate.editCompletedHandler(newMsgText)
}
onImageClicked: {
switch (mouse.button) {
case Qt.LeftButton:
root.imageClicked(image, mouse);
break;
case Qt.RightButton:
root.messageClickHandler(image, Qt.point(mouse.x, mouse.y), false, false, true, image, false, true, false, true, imageSource)
break;
}
}
onLinkActivated: {
if (link.startsWith('//')) {
const pubkey = link.replace("//", "");
Global.openProfilePopup(pubkey)
return;
}
Global.openLink(link)
}
onProfilePictureClicked: {
d.setMessageActive(root.messageId, true);
root.messageClickHandler(sender, Qt.point(mouse.x, mouse.y), true);
}
onReplyProfileClicked: {
d.setMessageActive(root.messageId, true);
root.messageClickHandler(sender, Qt.point(mouse.x, mouse.y), true, false, false, null, false, false, true);
}
onSenderNameClicked: {
d.setMessageActive(root.messageId, true);
root.messageClickHandler(sender, Qt.point(mouse.x, mouse.y), true);
}
onToggleReactionClicked: {
if (root.isChatBlocked)
return
if (!root.messageStore) {
console.error("Reaction can not be toggled, message store is not valid")
return
}
root.messageStore.toggleReaction(root.messageId, emojiId)
}
onAddReactionClicked: {
if (root.isChatBlocked)
return;
d.setMessageActive(root.messageId, true);
root.messageClickHandler(sender, Qt.point(mouse.x, mouse.y), false, false, false, null, true, false);
}
onStickerClicked: {
root.openStickerPackPopup(root.stickerPack);
}
mouseArea {
acceptedButtons: root.activityCenterMessage ? Qt.LeftButton : Qt.RightButton
enabled: !root.isChatBlocked &&
!root.placeholderMessage &&
delegate.contentType !== StatusMessage.ContentType.Image
onClicked: {
d.setMessageActive(root.messageId, true);
root.messageClickHandler(this, Qt.point(mouse.x, mouse.y),
false, false, false, null, root.isEmoji, false, false, false, "");
}
}
messageDetails: StatusMessageDetails {
contentType: delegate.contentType
messageText: root.messageText
messageContent: {
switch (delegate.contentType)
{
case StatusMessage.ContentType.Sticker:
return root.sticker;
case StatusMessage.ContentType.Image:
return root.messageImage;
}
return "";
}
amISender: root.amISender
sender.id: root.senderId
sender.userName: root.senderDisplayName
sender.localName: root.senderLocalName
sender.ensName: root.senderEnsName
sender.isContact: root.senderIsAdded
sender.trustIndicator: root.senderTrustStatus
sender.profileImage {
width: 40
height: 40
pubkey: root.senderId
source: root.senderIcon || ""
colorId: Utils.colorIdForPubkey(root.senderId)
colorHash: Utils.getColorHashAsJson(root.senderId)
}
}
replyDetails: StatusMessageDetails {
messageText: delegate.replyMessage ? delegate.replyMessage.messageText : ""
contentType: delegate.replyMessage ? delegate.convertContentType(delegate.replyMessage.contentType) : 0
messageContent: {
if (!delegate.replyMessage)
return "";
switch (contentType) {
case StatusMessage.ContentType.Sticker:
return delegate.replyMessage.sticker;
case StatusMessage.ContentType.Image:
return delegate.replyMessage.messageImage;
}
return "";
}
amISender: delegate.replyMessage && delegate.replyMessage.amISender
sender.id: delegate.replyMessage ? delegate.replyMessage.senderId : ""
sender.isContact: delegate.replyMessage && delegate.replyMessage.senderIsAdded
sender.userName: delegate.replyMessage ? delegate.replyMessage.senderDisplayName: ""
sender.ensName: delegate.replyMessage && delegate.replyMessage.ensVerified ? delegate.replyMessage.senderDisplayName : ""
sender.localName: delegate.replyMessage ? delegate.replyMessage.senderLocalName : ""
sender.profileImage {
width: 20
height: 20
pubkey: delegate.replySenderId
source: delegate.replyMessage ? delegate.replyMessage.senderIcon: ""
colorId: Utils.colorIdForPubkey(delegate.replySenderId)
colorHash: Utils.getColorHashAsJson(delegate.replySenderId)
}
}
statusChatInput: StatusChatInput {
id: editTextInput
readonly property string messageText: editTextInput.textInput.text
// TODO: Move this property and Escape handler to StatusChatInput
property bool suggestionsOpened: false
width: parent.width
Keys.onEscapePressed: {
if (!suggestionsOpened) {
delegate.editCancelled()
}
suggestionsOpened = false
}
store: root.rootStore
usersStore: root.usersStore
emojiPopup: root.emojiPopup
messageContextMenu: root.messageContextMenu
chatType: root.messageStore.getChatType()
isEdit: true
onSendMessage: {
delegate.editCompletedHandler(editTextInput.textInput.text)
}
suggestions.onVisibleChanged: {
if (suggestions.visible) {
suggestionsOpened = true
}
}
Component.onCompleted: {
parseMessage(root.messageText);
}
}
linksComponent: Component {
LinksMessageView {
linkUrls: root.linkUrls
container: root
messageStore: root.messageStore
store: root.rootStore
isCurrentUser: root.amISender
}
}
transcationComponent: Component {
TransactionBubbleView {
transactionParams: root.transactionParams
store: root.rootStore
contactsStore: root.contactsStore
}
}
invitationComponent: Component {
InvitationBubbleView {
store: root.rootStore
communityId: root.communityId
}
}
quickActions: [
Loader {
active: !root.isInPinnedPopup
sourceComponent: StatusFlatRoundButton {
width: d.chatButtonSize
height: d.chatButtonSize
icon.name: "reaction-b"
type: StatusFlatRoundButton.Type.Tertiary
tooltip.text: qsTr("Add reaction")
onClicked: {
d.setMessageActive(root.messageId, true)
root.messageClickHandler(this, Qt.point(mouse.x, mouse.y), false, false, false, null, true, false)
}
}
},
Loader {
active: !root.isInPinnedPopup
sourceComponent: StatusFlatRoundButton {
width: d.chatButtonSize
height: d.chatButtonSize
icon.name: "reply"
type: StatusFlatRoundButton.Type.Tertiary
tooltip.text: qsTr("Reply")
onClicked: {
root.showReplyArea(root.messageId, root.senderId)
if (messageContextMenu.closeParentPopup) {
messageContextMenu.closeParentPopup()
}
}
}
},
Loader {
active: !root.isInPinnedPopup && root.isText && !root.editModeOn && root.amISender
visible: active
sourceComponent: StatusFlatRoundButton {
width: d.chatButtonSize
height: d.chatButtonSize
icon.name: "edit_pencil"
type: StatusFlatRoundButton.Type.Tertiary
tooltip.text: qsTr("Edit")
onClicked: {
root.messageStore.setEditModeOn(root.messageId)
}
}
},
Loader {
active: {
if (!root.messageStore)
return false
const chatType = root.messageStore.getChatType();
const amIChatAdmin = root.messageStore.amIChatAdmin();
const pinMessageAllowedForMembers = root.messageStore.pinMessageAllowedForMembers()
return chatType === Constants.chatType.oneToOne ||
chatType === Constants.chatType.privateGroupChat && amIChatAdmin ||
chatType === Constants.chatType.communityChat && (amIChatAdmin || pinMessageAllowedForMembers);
}
sourceComponent: StatusFlatRoundButton {
width: d.chatButtonSize
height: d.chatButtonSize
icon.name: root.pinnedMessage ? "unpin" : "pin"
type: StatusFlatRoundButton.Type.Tertiary
tooltip.text: root.pinnedMessage ? qsTr("Unpin") : qsTr("Pin")
onClicked: {
if (root.pinnedMessage) {
messageStore.unpinMessage(root.messageId)
return;
}
if (d.canPin) {
messageStore.pinMessage(root.messageId)
return;
}
if (!chatContentModule) {
console.warn("error on open pinned messages limit reached from message context menu - chat content module is not set")
return;
}
Global.openPopup(pinnedMessagesPopupComponent, {
store: root.rootStore,
messageStore: messageStore,
pinnedMessagesModel: chatContentModule.pinnedMessagesModel,
messageToPin: root.messageId
});
}
}
},
Loader {
active: {
if (root.isInPinnedPopup)
return false;
if (!root.messageStore)
return false;
const isMyMessage = senderId !== "" && senderId === userProfile.pubKey;
const chatType = root.messageStore.getChatType();
return isMyMessage &&
(messageContentType === Constants.messageContentType.messageType ||
messageContentType === Constants.messageContentType.stickerType ||
messageContentType === Constants.messageContentType.emojiType ||
messageContentType === Constants.messageContentType.imageType ||
messageContentType === Constants.messageContentType.audioType);
}
sourceComponent: StatusFlatRoundButton {
width: d.chatButtonSize
height: d.chatButtonSize
icon.name: "delete"
type: StatusFlatRoundButton.Type.Tertiary
tooltip.text: qsTr("Delete")
onClicked: {
if (!localAccountSensitiveSettings.showDeleteMessageWarning) {
messageStore.deleteMessage(root.messageId)
}
else {
Global.openPopup(deleteMessageConfirmationDialogComponent)
}
}
}
}
]
}
}
Component {
id: deleteMessageConfirmationDialogComponent
ConfirmationDialog {
header.title: qsTrId("Confirm deleting this message")
confirmationText: qsTrId("Are you sure you want to delete this message? Be aware that other clients are not guaranteed to delete the message as well.")
height: 260
checkbox.visible: true
executeConfirm: function () {
if (checkbox.checked) {
localAccountSensitiveSettings.showDeleteMessageWarning = false
}
close()
messageStore.deleteMessage(root.messageId)
}
onClosed: {
destroy()
}
}
}
}

View File

@ -250,7 +250,7 @@ Item {
StyledText {
id: timeText
color: Style.current.secondaryText
text: Utils.formatShortTime(timestamp, RootStore.accountSensitiveSettings.is24hTimeFormat)
text: Utils.formatShortTime(messageTimestamp, RootStore.accountSensitiveSettings.is24hTimeFormat)
anchors.left: bubbleLoader.active ? bubbleLoader.right : undefined
anchors.leftMargin: bubbleLoader.active ? 13 : 0
anchors.right: bubbleLoader.active ? undefined : parent.right

File diff suppressed because it is too large Load Diff

View File

@ -478,6 +478,11 @@ QtObject {
readonly property string ens_connected: "connected"
readonly property string ens_connected_dkey: "connected-different-key"
readonly property string storeToKeychainValueStore: "store"
readonly property string storeToKeychainValueNotNow: "notNow"
readonly property string storeToKeychainValueNever: "never"
// WARNING: Remove later. Moved to StatusQ.
readonly property string editLabel: ` <span class="isEdited">` + qsTr("(edited)") + `</span>`
readonly property string newBookmark: " "

View File

@ -4,6 +4,7 @@ import QtQuick 2.13
import shared 1.0
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
QtObject {
property var globalUtilsInst: globalUtils
@ -56,55 +57,6 @@ QtObject {
return Style.current.accountColors[colorIndex]
}
function getMessageWithStyle(msg, isCurrentUser, hoveredLink = "") {
return `<style type="text/css">` +
`img, a, del, code, blockquote { margin: 0; padding: 0; }` +
`code {` +
`font-family: ${Style.current.fontCodeRegular.name};` +
`font-weight: 400;` +
`font-size: ${Style.current.secondaryTextFontSize};` +
`padding: 2px 4px;` +
`border-radius: 4px;` +
`background-color: ${Style.current.codeBackground};` +
`color: ${Style.current.black};` +
`white-space: pre;` +
`}` +
`p {` +
`line-height: 22px;` +
`}` +
`a {` +
`color: ${Style.current.linkColor};` +
`}` +
`a.mention {` +
`color: ${Style.current.mentionColor};` +
`background-color: ${Style.current.mentionBgColor};` +
`text-decoration: none;` +
`padding: 0px 2px;` +
`}` +
(hoveredLink !== "" ? `a.mention[href="${hoveredLink}"] { background-color: ${Style.current.mentionBgHoverColor}; }` : ``) +
`del {` +
`text-decoration: line-through;` +
`}` +
`table.blockquote td {` +
`padding-left: 10px;` +
`color: ${isCurrentUser ? Style.current.chatReplyCurrentUser : Style.current.secondaryText};` +
`}` +
`table.blockquote td.quoteline {` +
`background-color: ${isCurrentUser ? Style.current.chatReplyCurrentUser : Style.current.secondaryText};` +
`height: 100%;` +
`padding-left: 0;` +
`}` +
`.emoji {` +
`vertical-align: bottom;` +
`}` +
`span.isEdited {` +
`color: ${Style.current.secondaryText};` +
`margin-left: 5px` +
`}` +
`</style>` +
`${msg}`
}
function getReplyMessageStyle(msg, isCurrentUser) {
return `<style type="text/css">`+
`a {`+
@ -147,22 +99,6 @@ QtObject {
return addr.substring(0, 2 + numberOfChars) + "..." + addr.substring(addr.length - numberOfChars);
}
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 toLocaleString(val, locale, options) {
return NumberPolyFill.toLocaleString(val, locale, options)
}
@ -656,11 +592,7 @@ QtObject {
return ""
}
let compressedPk = getCompressedPk(publicKey)
return elideText(compressedPk, 6, 3)
}
function elideText(text, leftCharsCount, rightCharsCount = leftCharsCount) {
return text.substr(0, leftCharsCount) + "..." + text.substr(text.length - rightCharsCount)
return StatusQUtils.Utils.elideText(compressedPk, 6, 3)
}
function getTimeDifference(d1, d2) {