fix: Create only one instance of `StatusChatInput` (#10928)
* Chat input area preserved properties * Fix emoji/gif/stickers popups open/close logic
This commit is contained in:
parent
dc20651a97
commit
bc4492b53a
|
@ -21,6 +21,7 @@ QtObject:
|
|||
isUntrustworthy: bool
|
||||
isContact: bool
|
||||
active: bool
|
||||
blocked: bool
|
||||
|
||||
proc delete*(self: ChatDetails) =
|
||||
self.QObject.delete
|
||||
|
@ -32,7 +33,7 @@ QtObject:
|
|||
proc setChatDetails*(self: ChatDetails, id: string, `type`: int, belongsToCommunity,
|
||||
isUsersListAvailable: bool, name, icon: string, color, description,
|
||||
emoji: string, hasUnreadMessages: bool, notificationsCount: int, muted: bool, position: int,
|
||||
isUntrustworthy: bool, isContact: bool = false) =
|
||||
isUntrustworthy: bool, isContact: bool = false, blocked: bool = false) =
|
||||
self.id = id
|
||||
self.`type` = `type`
|
||||
self.belongsToCommunity = belongsToCommunity
|
||||
|
@ -49,6 +50,7 @@ QtObject:
|
|||
self.isUntrustworthy = isUntrustworthy
|
||||
self.isContact = isContact
|
||||
self.active = false
|
||||
self.blocked = blocked
|
||||
|
||||
proc getId(self: ChatDetails): string {.slot.} =
|
||||
return self.id
|
||||
|
@ -201,3 +203,14 @@ QtObject:
|
|||
proc setActive*(self: ChatDetails, value: bool) =
|
||||
self.active = value
|
||||
self.activeChanged()
|
||||
|
||||
proc blockedChanged(self: ChatDetails) {.signal.}
|
||||
proc getBlocked(self: ChatDetails): bool {.slot.} =
|
||||
return self.blocked
|
||||
QtProperty[bool] blocked:
|
||||
read = getBlocked
|
||||
notify = blockedChanged
|
||||
|
||||
proc setBlocked*(self: ChatDetails, value: bool) =
|
||||
self.blocked = value
|
||||
self.blockedChanged()
|
||||
|
|
|
@ -142,11 +142,13 @@ proc init*(self: Controller) =
|
|||
var args = ContactArgs(e)
|
||||
if (args.contactId == self.chatId):
|
||||
self.delegate.onMutualContactChanged()
|
||||
self.delegate.onContactDetailsUpdated(args.contactId)
|
||||
|
||||
self.events.on(SIGNAL_CONTACT_UNBLOCKED) do(e: Args):
|
||||
var args = ContactArgs(e)
|
||||
if (args.contactId == self.chatId):
|
||||
self.delegate.onMutualContactChanged()
|
||||
self.delegate.onContactDetailsUpdated(args.contactId)
|
||||
|
||||
self.events.on(SIGNAL_MESSAGE_DELETION) do(e: Args):
|
||||
let args = MessageDeletedArgs(e)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import NimQml
|
||||
|
||||
QtObject:
|
||||
type
|
||||
Item* = ref object of QObject
|
||||
|
||||
proc setup(self: Item) =
|
||||
self.QObject.setup
|
||||
|
||||
proc delete*(self: Item) =
|
||||
self.QObject.delete
|
||||
|
||||
proc newItem*(): Item =
|
||||
new(result, delete)
|
||||
result.setup()
|
||||
|
||||
proc id*(self: Item): string {.slot.} =
|
||||
self.id
|
||||
|
||||
QtProperty[string] id:
|
||||
read = id
|
|
@ -1,17 +0,0 @@
|
|||
import NimQml
|
||||
import item
|
||||
|
||||
QtObject:
|
||||
type
|
||||
Model* = ref object of QAbstractListModel
|
||||
sections: seq[Item]
|
||||
|
||||
proc setup(self: Model) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc delete*(self: Model) =
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc newModel*(): Model =
|
||||
new(result, delete)
|
||||
result.setup
|
|
@ -0,0 +1,58 @@
|
|||
import NimQml
|
||||
|
||||
QtObject:
|
||||
type
|
||||
PreservedProperties* = ref object of QObject
|
||||
text: string
|
||||
replyMessageId: string
|
||||
fileUrlsAndSourcesJson: string
|
||||
|
||||
proc setup(self: PreservedProperties) =
|
||||
self.QObject.setup
|
||||
self.fileUrlsAndSourcesJson = "[]"
|
||||
|
||||
proc delete*(self: PreservedProperties) =
|
||||
self.QObject.delete
|
||||
|
||||
proc newPreservedProperties*(): PreservedProperties =
|
||||
new(result, delete)
|
||||
result.QObject.setup
|
||||
|
||||
proc textChanged*(self: PreservedProperties) {.signal.}
|
||||
proc setText*(self: PreservedProperties, value: string) {.slot.} =
|
||||
if self.text == value:
|
||||
return
|
||||
self.text = value
|
||||
self.textChanged()
|
||||
proc getText*(self: PreservedProperties): string {.slot.} =
|
||||
result = self.text
|
||||
QtProperty[string] text:
|
||||
read = getText
|
||||
write = setText
|
||||
notify = textChanged
|
||||
|
||||
proc replyMessageIdChanged*(self: PreservedProperties) {.signal.}
|
||||
proc setReplyMessageId*(self: PreservedProperties, value: string) {.slot.}=
|
||||
if self.replyMessageId == value:
|
||||
return
|
||||
self.replyMessageId = value
|
||||
self.replyMessageIdChanged()
|
||||
proc getReplyMessageId*(self: PreservedProperties): string {.slot.} =
|
||||
result = self.replyMessageId
|
||||
QtProperty[string] replyMessageId:
|
||||
read = getReplyMessageId
|
||||
write = setReplyMessageId
|
||||
notify = replyMessageIdChanged
|
||||
|
||||
proc fileUrlsAndSourcesJsonChanged*(self: PreservedProperties) {.signal.}
|
||||
proc setFileUrlsAndSourcesJson*(self: PreservedProperties, value: string) {.slot.}=
|
||||
if self.fileUrlsAndSourcesJson == value:
|
||||
return
|
||||
self.fileUrlsAndSourcesJson = value
|
||||
self.fileUrlsAndSourcesJsonChanged()
|
||||
proc getFileUrlsAndSourcesJson*(self: PreservedProperties): string {.slot.} =
|
||||
result = self.fileUrlsAndSourcesJson
|
||||
QtProperty[string] fileUrlsAndSourcesJson:
|
||||
read = getFileUrlsAndSourcesJson
|
||||
write = setFileUrlsAndSourcesJson
|
||||
notify = fileUrlsAndSourcesJsonChanged
|
|
@ -1,32 +1,38 @@
|
|||
import NimQml
|
||||
import ./model
|
||||
import ./io_interface
|
||||
import ./gif_column_model
|
||||
import ./preserved_properties
|
||||
import ../../../../../../app_service/service/gif/dto
|
||||
|
||||
QtObject:
|
||||
type
|
||||
View* = ref object of QObject
|
||||
delegate: io_interface.AccessInterface
|
||||
model: Model
|
||||
gifColumnAModel: GifColumnModel
|
||||
gifColumnBModel: GifColumnModel
|
||||
gifColumnCModel: GifColumnModel
|
||||
gifLoading: bool
|
||||
preservedProperties: PreservedProperties
|
||||
preservedPropertiesVariant: QVariant
|
||||
|
||||
proc delete*(self: View) =
|
||||
self.model.delete
|
||||
self.QObject.delete
|
||||
self.gifColumnAModel.delete
|
||||
self.gifColumnBModel.delete
|
||||
self.gifColumnCModel.delete
|
||||
self.preservedProperties.delete
|
||||
self.preservedPropertiesVariant.delete
|
||||
|
||||
proc newView*(delegate: io_interface.AccessInterface): View =
|
||||
new(result, delete)
|
||||
result.QObject.setup
|
||||
result.delegate = delegate
|
||||
result.model = newModel()
|
||||
result.gifColumnAModel = newGifColumnModel()
|
||||
result.gifColumnBModel = newGifColumnModel()
|
||||
result.gifColumnCModel = newGifColumnModel()
|
||||
result.gifLoading = false
|
||||
result.preservedProperties = newPreservedProperties()
|
||||
result.preservedPropertiesVariant = newQVariant(result.preservedProperties)
|
||||
|
||||
proc load*(self: View) =
|
||||
self.delegate.viewDidLoad()
|
||||
|
@ -171,3 +177,9 @@ QtObject:
|
|||
proc isFavorite*(self: View, id: string): bool {.slot.} =
|
||||
let gifItem = self.findGifDto(id)
|
||||
return self.delegate.isFavorite(gifItem)
|
||||
|
||||
proc getPreservedProperties(self: View): QVariant {.slot.} =
|
||||
return self.preservedPropertiesVariant
|
||||
|
||||
QtProperty[QVariant] preservedProperties:
|
||||
read = getPreservedProperties
|
||||
|
|
|
@ -358,6 +358,7 @@ method onContactDetailsUpdated*(self: Module, contactId: string) =
|
|||
if(self.controller.getMyChatId() == contactId):
|
||||
self.view.updateChatDetailsNameAndIcon(updatedContact.defaultDisplayName, updatedContact.icon)
|
||||
self.view.updateTrustStatus(updatedContact.dto.trustStatus == TrustStatus.Untrustworthy)
|
||||
self.view.updateChatBlocked(updatedContact.dto.blocked)
|
||||
|
||||
method onNotificationsUpdated*(self: Module, hasUnreadMessages: bool, notificationCount: int) =
|
||||
self.view.updateChatDetailsNotifications(hasUnreadMessages, notificationCount)
|
||||
|
|
|
@ -146,4 +146,7 @@ QtObject:
|
|||
self.chatDetails.setIsMutualContact(value)
|
||||
|
||||
proc downloadMessages*(self: View, filePath: string) {.slot.} =
|
||||
self.delegate.downloadMessages(filePath)
|
||||
self.delegate.downloadMessages(filePath)
|
||||
|
||||
proc updateChatBlocked*(self: View, blocked: bool) =
|
||||
self.chatDetails.setBlocked(blocked)
|
||||
|
|
|
@ -62,7 +62,7 @@ Rectangle {
|
|||
anchors.bottomMargin: 8
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
color: Theme.palette.white
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQml 2.14
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml 2.15
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
@ -18,6 +19,7 @@ import "../controls"
|
|||
import "../popups"
|
||||
import "../panels"
|
||||
import "../../Wallet"
|
||||
import "../stores"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
@ -40,7 +42,6 @@ Item {
|
|||
readonly property var contactDetails: rootStore ? rootStore.oneToOneChatContact : null
|
||||
readonly property bool isUserAdded: root.contactDetails && root.contactDetails.isAdded
|
||||
|
||||
signal openAppSearch()
|
||||
signal openStickerPackPopup(string stickerPackId)
|
||||
|
||||
function requestAddressForTransaction(address, amount, tokenAddress, tokenDecimals = 18) {
|
||||
|
@ -98,6 +99,84 @@ Item {
|
|||
root.createChatPropertiesStore.resetProperties()
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property var activeChatContentModule: d.getChatContentModule(root.activeChatId)
|
||||
|
||||
readonly property UsersStore activeUsersStore: UsersStore {
|
||||
usersModule: !!d.activeChatContentModule ? d.activeChatContentModule.usersModule : null
|
||||
}
|
||||
|
||||
readonly property MessageStore activeMessagesStore: MessageStore {
|
||||
messageModule: d.activeChatContentModule ? d.activeChatContentModule.messagesModule : null
|
||||
chatSectionModule: root.rootStore.chatCommunitySectionModule
|
||||
}
|
||||
|
||||
function getChatContentModule(chatId) {
|
||||
root.parentModule.prepareChatContentModuleForChatId(chatId)
|
||||
return root.parentModule.getChatContentModule()
|
||||
}
|
||||
|
||||
function showReplyArea(messageId) {
|
||||
const obj = d.activeMessagesStore.getMessageByIdAsJson(messageId)
|
||||
if (!obj)
|
||||
return
|
||||
chatInput.showReplyArea(messageId,
|
||||
obj.senderDisplayName,
|
||||
obj.messageText,
|
||||
obj.contentType,
|
||||
obj.messageImage,
|
||||
obj.albumMessageImages,
|
||||
obj.albumImagesCount,
|
||||
obj.sticker)
|
||||
}
|
||||
|
||||
function restoreInputReply() {
|
||||
const replyMessageId = d.activeChatContentModule.inputAreaModule.preservedProperties.replyMessageId
|
||||
if (replyMessageId)
|
||||
d.showReplyArea(replyMessageId)
|
||||
else
|
||||
chatInput.resetReplyArea()
|
||||
}
|
||||
|
||||
function restoreInputAttachments() {
|
||||
const filesJson = d.activeChatContentModule.inputAreaModule.preservedProperties.fileUrlsAndSourcesJson
|
||||
let filesList = []
|
||||
if (filesJson) {
|
||||
try {
|
||||
filesList = JSON.parse(filesJson)
|
||||
} catch(e) {
|
||||
console.error("failed to parse preserved fileUrlsAndSources")
|
||||
}
|
||||
}
|
||||
chatInput.resetImageArea()
|
||||
chatInput.validateImagesAndShowImageArea(filesList)
|
||||
}
|
||||
|
||||
function restoreInputState() {
|
||||
|
||||
if (!d.activeChatContentModule) {
|
||||
chatInput.textInput.text = ""
|
||||
chatInput.resetReplyArea()
|
||||
chatInput.resetImageArea()
|
||||
return
|
||||
}
|
||||
|
||||
// Restore message text
|
||||
chatInput.textInput.text = d.activeChatContentModule.inputAreaModule.preservedProperties.text
|
||||
chatInput.textInput.cursorPosition = chatInput.textInput.length
|
||||
|
||||
d.restoreInputReply()
|
||||
d.restoreInputAttachments()
|
||||
}
|
||||
|
||||
onActiveChatContentModuleChanged: {
|
||||
// Call later to make sure activeUsersStore and activeMessagesStore bindings are updated
|
||||
Qt.callLater(d.restoreInputState)
|
||||
}
|
||||
}
|
||||
|
||||
EmptyChatPanel {
|
||||
anchors.fill: parent
|
||||
visible: root.activeChatId === "" || root.chatsCount == 0
|
||||
|
@ -107,46 +186,150 @@ Item {
|
|||
|
||||
// This is kind of a solution for applying backend refactored changes with the minimal qml changes.
|
||||
// The best would be if we made qml to follow the struct we have on the backend side.
|
||||
Repeater {
|
||||
id: chatRepeater
|
||||
model: parentModule && parentModule.model
|
||||
|
||||
ChatContentView {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
visible: !root.rootStore.openCreateChat && isActiveChannel
|
||||
chatId: model.itemId
|
||||
chatType: model.type
|
||||
chatMessagesLoader.active: model.loaderActive
|
||||
rootStore: root.rootStore
|
||||
contactsStore: root.contactsStore
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Repeater {
|
||||
id: chatRepeater
|
||||
model: parentModule && parentModule.model
|
||||
|
||||
ChatContentView {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
visible: !root.rootStore.openCreateChat && model.active
|
||||
chatId: model.itemId
|
||||
chatType: model.type
|
||||
chatMessagesLoader.active: model.loaderActive
|
||||
rootStore: root.rootStore
|
||||
contactsStore: root.contactsStore
|
||||
emojiPopup: root.emojiPopup
|
||||
stickersPopup: root.stickersPopup
|
||||
stickersLoaded: root.stickersLoaded
|
||||
isBlocked: model.blocked
|
||||
onOpenStickerPackPopup: {
|
||||
root.openStickerPackPopup(stickerPackId)
|
||||
}
|
||||
onShowReplyArea: (messageId) => {
|
||||
d.showReplyArea(messageId)
|
||||
}
|
||||
onForceInputFocus: {
|
||||
chatInput.forceInputActiveFocus()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
chatContentModule = d.getChatContentModule(model.itemId)
|
||||
chatSectionModule = root.parentModule
|
||||
root.checkForCreateChatOptions(model.itemId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusChatInput {
|
||||
id: chatInput
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.current.smallPadding
|
||||
|
||||
enabled: root.rootStore.sectionDetails.joined && !root.rootStore.sectionDetails.amIBanned &&
|
||||
root.rootStore.isUserAllowedToSendMessage
|
||||
|
||||
store: root.rootStore
|
||||
usersStore: d.usersStore
|
||||
|
||||
textInput.placeholderText: {
|
||||
if (d.activeChatContentModule.chatDetails.blocked)
|
||||
return qsTr("This user has been blocked.")
|
||||
if (!root.rootStore.sectionDetails.joined || root.rootStore.sectionDetails.amIBanned)
|
||||
return qsTr("You need to join this community to send messages")
|
||||
return root.rootStore.chatInputPlaceHolderText
|
||||
}
|
||||
|
||||
emojiPopup: root.emojiPopup
|
||||
stickersPopup: root.stickersPopup
|
||||
sendTransactionNoEnsModal: cmpSendTransactionNoEns
|
||||
receiveTransactionModal: cmpReceiveTransaction
|
||||
sendTransactionWithEnsModal: cmpSendTransactionWithEns
|
||||
stickersLoaded: root.stickersLoaded
|
||||
isBlocked: model.blocked
|
||||
isActiveChannel: model.active
|
||||
onOpenStickerPackPopup: {
|
||||
root.openStickerPackPopup(stickerPackId)
|
||||
isContactBlocked: d.activeChatContentModule.chatDetails.blocked
|
||||
chatType: root.activeChatType
|
||||
suggestions.suggestionFilter.addSystemSuggestions: chatType === Constants.chatType.communityChat
|
||||
|
||||
textInput.onTextChanged: {
|
||||
d.activeChatContentModule.inputAreaModule.preservedProperties.text = textInput.text
|
||||
}
|
||||
onOpenAppSearch: {
|
||||
root.openAppSearch();
|
||||
|
||||
onReplyMessageIdChanged: {
|
||||
d.activeChatContentModule.inputAreaModule.preservedProperties.replyMessageId = replyMessageId
|
||||
}
|
||||
Component.onCompleted: {
|
||||
parentModule.prepareChatContentModuleForChatId(model.itemId)
|
||||
chatContentModule = parentModule.getChatContentModule()
|
||||
chatSectionModule = root.parentModule
|
||||
root.checkForCreateChatOptions(model.itemId)
|
||||
|
||||
onFileUrlsAndSourcesChanged: {
|
||||
d.activeChatContentModule.inputAreaModule.preservedProperties.fileUrlsAndSourcesJson = JSON.stringify(chatInput.fileUrlsAndSources)
|
||||
}
|
||||
|
||||
onSendTransactionCommandButtonClicked: {
|
||||
if (!d.activeChatContentModule) {
|
||||
console.warn("error on sending transaction command - chat content module is not set")
|
||||
return
|
||||
}
|
||||
|
||||
if (Utils.isEnsVerified(d.activeChatContentModule.getMyChatId())) {
|
||||
Global.openPopup(cmpSendTransactionWithEns)
|
||||
} else {
|
||||
Global.openPopup(cmpSendTransactionNoEns)
|
||||
}
|
||||
}
|
||||
|
||||
onReceiveTransactionCommandButtonClicked: {
|
||||
Global.openPopup(cmpReceiveTransaction)
|
||||
}
|
||||
|
||||
onStickerSelected: {
|
||||
root.rootStore.sendSticker(d.activeChatContentModule.getMyChatId(),
|
||||
hashId,
|
||||
chatInput.isReply ? chatInput.replyMessageId : "",
|
||||
packId,
|
||||
url)
|
||||
}
|
||||
|
||||
|
||||
onSendMessage: {
|
||||
if (!d.activeChatContentModule) {
|
||||
console.debug("error on sending message - chat content module is not set")
|
||||
return
|
||||
}
|
||||
|
||||
if (root.rootStore.sendMessage(d.activeChatContentModule.getMyChatId(),
|
||||
event,
|
||||
chatInput.getTextWithPublicKeys(),
|
||||
chatInput.isReply? chatInput.replyMessageId : "",
|
||||
chatInput.fileUrlsAndSources
|
||||
))
|
||||
{
|
||||
Global.playSendMessageSound()
|
||||
|
||||
chatInput.textInput.clear();
|
||||
chatInput.textInput.textFormat = TextEdit.PlainText;
|
||||
chatInput.textInput.textFormat = TextEdit.RichText;
|
||||
}
|
||||
}
|
||||
|
||||
onUnblockChat: {
|
||||
d.activeChatContentModule.unblockChat()
|
||||
}
|
||||
|
||||
onKeyUpPress: {
|
||||
d.activeMessagesStore.setEditModeOnLastMessage(root.rootStore.userProfileInst.pubKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: cmpSendTransactionNoEns
|
||||
ChatCommandModal {
|
||||
id: sendTransactionNoEns
|
||||
store: root.rootStore
|
||||
contactsStore: root.contactsStore
|
||||
onClosed: {
|
||||
|
@ -178,7 +361,6 @@ Item {
|
|||
Component {
|
||||
id: cmpReceiveTransaction
|
||||
ChatCommandModal {
|
||||
id: receiveTransaction
|
||||
store: root.rootStore
|
||||
contactsStore: root.contactsStore
|
||||
onClosed: {
|
||||
|
@ -209,7 +391,6 @@ Item {
|
|||
Component {
|
||||
id: cmpSendTransactionWithEns
|
||||
SendModal {
|
||||
id: sendTransactionWithEns
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
|
|
|
@ -24,16 +24,12 @@ import "../stores"
|
|||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
objectName: "chatContentViewColumn"
|
||||
spacing: 0
|
||||
|
||||
// Important:
|
||||
// Each chat/channel has its own ChatContentModule
|
||||
// Important: each chat/channel has its own ChatContentModule
|
||||
property var chatContentModule
|
||||
property var chatSectionModule
|
||||
property var rootStore
|
||||
property var contactsStore
|
||||
property bool isActiveChannel: false
|
||||
property string chatId
|
||||
property int chatType: Constants.chatType.unknown
|
||||
|
||||
|
@ -43,22 +39,27 @@ ColumnLayout {
|
|||
property var stickersPopup
|
||||
property UsersStore usersStore: UsersStore {}
|
||||
|
||||
onChatContentModuleChanged: if (!!chatContentModule) {
|
||||
root.usersStore.usersModule = root.chatContentModule.usersModule
|
||||
}
|
||||
|
||||
signal openAppSearch()
|
||||
signal openStickerPackPopup(string stickerPackId)
|
||||
|
||||
property Component sendTransactionNoEnsModal
|
||||
property Component receiveTransactionModal
|
||||
property Component sendTransactionWithEnsModal
|
||||
|
||||
property bool isBlocked: false
|
||||
property bool isUserAllowedToSendMessage: root.rootStore.isUserAllowedToSendMessage
|
||||
property string chatInputPlaceholder: root.rootStore.chatInputPlaceHolderText
|
||||
property bool stickersLoaded: false
|
||||
|
||||
readonly property var messageStore: MessageStore {
|
||||
messageModule: chatContentModule ? chatContentModule.messagesModule : null
|
||||
chatSectionModule: root.rootStore.chatCommunitySectionModule
|
||||
}
|
||||
|
||||
signal showReplyArea(messageId: string)
|
||||
signal forceInputFocus()
|
||||
|
||||
objectName: "chatContentViewColumn"
|
||||
spacing: 0
|
||||
|
||||
onChatContentModuleChanged: if (!!chatContentModule) {
|
||||
root.usersStore.usersModule = root.chatContentModule.usersModule
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
active: root.isBlocked
|
||||
|
@ -69,184 +70,33 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
|
||||
readonly property var messageStore: MessageStore {
|
||||
messageModule: chatContentModule ? chatContentModule.messagesModule : null
|
||||
chatSectionModule: root.rootStore.chatCommunitySectionModule
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property string blockedText: qsTr("This user has been blocked.")
|
||||
|
||||
function showReplyArea(messageId) {
|
||||
let obj = messageStore.getMessageByIdAsJson(messageId)
|
||||
if (!obj) {
|
||||
return
|
||||
}
|
||||
if (inputAreaLoader.item) {
|
||||
inputAreaLoader.item.chatInput.showReplyArea(messageId, obj.senderDisplayName, obj.messageText, obj.contentType, obj.messageImage, obj.albumMessageImages, obj.albumImagesCount, obj.sticker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Loader {
|
||||
id: chatMessagesLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
id: chatMessagesLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
sourceComponent: ChatMessagesView {
|
||||
chatContentModule: root.chatContentModule
|
||||
rootStore: root.rootStore
|
||||
contactsStore: root.contactsStore
|
||||
messageStore: root.messageStore
|
||||
emojiPopup: root.emojiPopup
|
||||
stickersPopup: root.stickersPopup
|
||||
usersStore: root.usersStore
|
||||
stickersLoaded: root.stickersLoaded
|
||||
chatId: root.chatId
|
||||
isOneToOne: root.chatType === Constants.chatType.oneToOne
|
||||
isContactBlocked: root.isBlocked
|
||||
isChatBlocked: root.isBlocked || !root.isUserAllowedToSendMessage
|
||||
channelEmoji: !chatContentModule ? "" : (chatContentModule.chatDetails.emoji || "")
|
||||
isActiveChannel: root.isActiveChannel
|
||||
onShowReplyArea: (messageId, senderId) => {
|
||||
d.showReplyArea(messageId)
|
||||
}
|
||||
onOpenStickerPackPopup: {
|
||||
root.openStickerPackPopup(stickerPackId);
|
||||
}
|
||||
onEditModeChanged: {
|
||||
if (!editModeOn && inputAreaLoader.item)
|
||||
inputAreaLoader.item.chatInput.forceInputActiveFocus()
|
||||
}
|
||||
sourceComponent: ChatMessagesView {
|
||||
chatContentModule: root.chatContentModule
|
||||
rootStore: root.rootStore
|
||||
contactsStore: root.contactsStore
|
||||
messageStore: root.messageStore
|
||||
emojiPopup: root.emojiPopup
|
||||
stickersPopup: root.stickersPopup
|
||||
usersStore: root.usersStore
|
||||
stickersLoaded: root.stickersLoaded
|
||||
chatId: root.chatId
|
||||
isOneToOne: root.chatType === Constants.chatType.oneToOne
|
||||
isChatBlocked: root.isBlocked || !root.isUserAllowedToSendMessage
|
||||
channelEmoji: !chatContentModule ? "" : (chatContentModule.chatDetails.emoji || "")
|
||||
onShowReplyArea: (messageId, senderId) => {
|
||||
root.showReplyArea(messageId)
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: inputAreaLoader
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
Layout.fillWidth: true
|
||||
|
||||
active: root.isActiveChannel
|
||||
asynchronous: true
|
||||
|
||||
property string preservedText
|
||||
Binding on preservedText {
|
||||
when: inputAreaLoader.item != null
|
||||
value: inputAreaLoader.item ? inputAreaLoader.item.chatInput.textInput.text : inputAreaLoader.preservedText
|
||||
restoreMode: Binding.RestoreNone
|
||||
delayed: true
|
||||
onOpenStickerPackPopup: {
|
||||
root.openStickerPackPopup(stickerPackId);
|
||||
}
|
||||
|
||||
// FIXME: `StatusChatInput` is way too heavy
|
||||
// see: https://github.com/status-im/status-desktop/pull/10343#issuecomment-1515103756
|
||||
sourceComponent: Item {
|
||||
id: inputArea
|
||||
implicitHeight: chatInput.implicitHeight
|
||||
+ chatInput.anchors.topMargin
|
||||
+ chatInput.anchors.bottomMargin
|
||||
|
||||
readonly property alias chatInput: chatInput
|
||||
|
||||
StatusChatInput {
|
||||
id: chatInput
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.current.smallPadding
|
||||
|
||||
// We enable the component if the contact is blocked, because if we disable it, the `Unban` button
|
||||
// becomes disabled. All the local components inside already disable themselves when blocked
|
||||
enabled: root.isBlocked ||
|
||||
(root.rootStore.sectionDetails.joined && !root.rootStore.sectionDetails.amIBanned &&
|
||||
root.isUserAllowedToSendMessage)
|
||||
|
||||
store: root.rootStore
|
||||
usersStore: root.usersStore
|
||||
|
||||
textInput.text: inputAreaLoader.preservedText
|
||||
textInput.placeholderText: root.isBlocked ? d.blockedText : root.chatInputPlaceholder
|
||||
emojiPopup: root.emojiPopup
|
||||
stickersPopup: root.stickersPopup
|
||||
isContactBlocked: root.isBlocked
|
||||
isActiveChannel: root.isActiveChannel
|
||||
anchors.bottom: parent.bottom
|
||||
chatType: root.chatType
|
||||
suggestions.suggestionFilter.addSystemSuggestions: chatType === Constants.chatType.communityChat
|
||||
|
||||
Binding on chatInputPlaceholder {
|
||||
when: root.isBlocked
|
||||
value: d.blockedText
|
||||
}
|
||||
|
||||
Binding on chatInputPlaceholder {
|
||||
when: !root.rootStore.sectionDetails.joined || root.rootStore.sectionDetails.amIBanned
|
||||
value: qsTr("You need to join this community to send messages")
|
||||
}
|
||||
|
||||
onSendTransactionCommandButtonClicked: {
|
||||
if(!chatContentModule) {
|
||||
console.debug("error on sending transaction command - chat content module is not set")
|
||||
return
|
||||
}
|
||||
|
||||
if (Utils.isEnsVerified(chatContentModule.getMyChatId())) {
|
||||
Global.openPopup(root.sendTransactionWithEnsModal)
|
||||
} else {
|
||||
Global.openPopup(root.sendTransactionNoEnsModal)
|
||||
}
|
||||
}
|
||||
onReceiveTransactionCommandButtonClicked: {
|
||||
Global.openPopup(root.receiveTransactionModal)
|
||||
}
|
||||
onStickerSelected: {
|
||||
root.rootStore.sendSticker(chatContentModule.getMyChatId(),
|
||||
hashId,
|
||||
chatInput.isReply ? chatInput.replyMessageId : "",
|
||||
packId,
|
||||
url)
|
||||
}
|
||||
|
||||
|
||||
onSendMessage: {
|
||||
if (!chatContentModule) {
|
||||
console.debug("error on sending message - chat content module is not set")
|
||||
return
|
||||
}
|
||||
|
||||
if(root.rootStore.sendMessage(chatContentModule.getMyChatId(),
|
||||
event,
|
||||
chatInput.getTextWithPublicKeys(),
|
||||
chatInput.isReply? chatInput.replyMessageId : "",
|
||||
chatInput.fileUrlsAndSources
|
||||
))
|
||||
{
|
||||
Global.playSendMessageSound()
|
||||
|
||||
chatInput.textInput.clear();
|
||||
chatInput.textInput.textFormat = TextEdit.PlainText;
|
||||
chatInput.textInput.textFormat = TextEdit.RichText;
|
||||
}
|
||||
}
|
||||
|
||||
onUnblockChat: {
|
||||
chatContentModule.unblockChat()
|
||||
}
|
||||
onKeyUpPress: messageStore.setEditModeOnLastMessage(root.rootStore.userProfileInst.pubKey)
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(() => {
|
||||
forceInputActiveFocus()
|
||||
textInput.cursorPosition = textInput.length
|
||||
})
|
||||
}
|
||||
}
|
||||
onEditModeChanged: {
|
||||
if (!editModeOn)
|
||||
root.forceInputFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -251,7 +251,6 @@ Item {
|
|||
chatLogView: ListView.view
|
||||
chatContentModule: root.chatContentModule
|
||||
|
||||
isActiveChannel: root.isActiveChannel
|
||||
isChatBlocked: root.isChatBlocked
|
||||
|
||||
chatId: root.chatId
|
||||
|
|
|
@ -89,9 +89,6 @@ StatusSectionLayout {
|
|||
onOpenStickerPackPopup: {
|
||||
Global.openPopup(statusStickerPackClickPopup, {packId: stickerPackId, store: root.stickersPopup.store} )
|
||||
}
|
||||
onOpenAppSearch: {
|
||||
root.openAppSearch();
|
||||
}
|
||||
}
|
||||
|
||||
showRightPanel: {
|
||||
|
|
|
@ -433,7 +433,6 @@ Item {
|
|||
enabled: communityData.amISectionAdmin
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: {
|
||||
console.log("<<< tapped")
|
||||
adminPopupMenu.showInviteButton = true
|
||||
adminPopupMenu.x = eventPoint.position.x + 4
|
||||
adminPopupMenu.y = eventPoint.position.y + 4
|
||||
|
|
|
@ -36,19 +36,16 @@ Rectangle {
|
|||
property var emojiPopup: null
|
||||
property var stickersPopup: null
|
||||
// Use this to only enable the Connections only when this Input opens the Emoji popup
|
||||
property bool emojiPopupOpened: false
|
||||
property bool stickersPopupOpened: false
|
||||
property bool closeGifPopupAfterSelection: true
|
||||
|
||||
property bool emojiEvent: false
|
||||
property bool isColonPressed: false
|
||||
property bool isReply: false
|
||||
property string replyMessageId: replyArea.messageId
|
||||
readonly property string replyMessageId: replyArea.messageId
|
||||
|
||||
property bool isImage: false
|
||||
property bool isEdit: false
|
||||
property bool isContactBlocked: false
|
||||
property bool isActiveChannel: false
|
||||
|
||||
property int messageLimit: 2000
|
||||
property int messageLimitVisible: 200
|
||||
|
@ -131,36 +128,52 @@ Rectangle {
|
|||
property int leftOfMentionIndex: -1
|
||||
property int rightOfMentionIndex: -1
|
||||
readonly property int nbEmojisInClipboard: StatusQUtils.Emoji.nbEmojis(QClipboardProxy.html)
|
||||
|
||||
property bool emojiPopupOpened: false
|
||||
property bool stickersPopupOpened: false
|
||||
|
||||
// common popups are emoji, jif and stickers
|
||||
// Put controlWidth as argument with default value for binding
|
||||
function getCommonPopupRelativePosition(popup, popupParent, controlWidth = control.width) {
|
||||
const controlX = controlWidth - emojiPopup.width - Style.current.halfPadding
|
||||
const controlY = -emojiPopup.height
|
||||
return popupParent.mapFromItem(control, controlX, controlY)
|
||||
}
|
||||
|
||||
readonly property point emojiPopupPosition: getCommonPopupRelativePosition(emojiPopup, emojiBtn)
|
||||
readonly property point stickersPopupPosition: getCommonPopupRelativePosition(stickersPopup, stickersBtn)
|
||||
|
||||
readonly property StateGroup emojiPopupTakeover: StateGroup {
|
||||
states: State {
|
||||
when: control.emojiPopupOpened
|
||||
when: d.emojiPopupOpened
|
||||
|
||||
PropertyChanges {
|
||||
target: emojiPopup
|
||||
|
||||
parent: control
|
||||
parent: emojiBtn
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
x: control.width - emojiPopup.width - Style.current.halfPadding
|
||||
y: -emojiPopup.height
|
||||
x: d.emojiPopupPosition.x
|
||||
y: d.emojiPopupPosition.y
|
||||
}
|
||||
}
|
||||
}
|
||||
readonly property StateGroup stickersPopupTakeover: StateGroup {
|
||||
states: State {
|
||||
when: control.stickersPopupOpened
|
||||
when: d.stickersPopupOpened
|
||||
|
||||
PropertyChanges {
|
||||
target: stickersPopup
|
||||
|
||||
parent: control
|
||||
parent: stickersBtn
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
x: control.width - stickersPopup.width - Style.current.halfPadding
|
||||
y: -stickersPopup.height
|
||||
x: d.stickersPopupPosition.x
|
||||
y: d.stickersPopupPosition.y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property bool chatCommandsPopupOpen: false
|
||||
property Menu textFormatMenu: null
|
||||
|
||||
function copyMentions(start, end) {
|
||||
copiedMentionsPos = []
|
||||
|
@ -268,30 +281,8 @@ Rectangle {
|
|||
messageInputField.insert(start, text.replace(/\n/g, "<br/>"));
|
||||
}
|
||||
|
||||
function togglePopup(popup, btn) {
|
||||
if (popup !== control.stickersPopup) {
|
||||
control.stickersPopup.close()
|
||||
}
|
||||
|
||||
if (popup !== gifPopup) {
|
||||
gifPopup.close()
|
||||
}
|
||||
|
||||
if (popup !== emojiPopup) {
|
||||
emojiPopup.close()
|
||||
}
|
||||
|
||||
if (popup.opened) {
|
||||
popup.close()
|
||||
btn.highlighted = false
|
||||
} else {
|
||||
popup.open()
|
||||
btn.highlighted = true
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
enabled: control.emojiPopupOpened
|
||||
enabled: d.emojiPopupOpened
|
||||
target: emojiPopup
|
||||
|
||||
function onEmojiSelected(text: string, atCursor: bool) {
|
||||
|
@ -300,13 +291,12 @@ Rectangle {
|
|||
messageInputField.forceActiveFocus();
|
||||
}
|
||||
function onClosed() {
|
||||
emojiBtn.highlighted = false
|
||||
control.emojiPopupOpened = false
|
||||
d.emojiPopupOpened = false
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
enabled: control.stickersPopupOpened
|
||||
enabled: d.stickersPopupOpened
|
||||
target: control.stickersPopup
|
||||
|
||||
function onStickerSelected(hashId: string, packId: string, url: string ) {
|
||||
|
@ -315,15 +305,14 @@ Rectangle {
|
|||
messageInputField.forceActiveFocus();
|
||||
}
|
||||
function onClosed() {
|
||||
stickersBtn.highlighted = false
|
||||
control.stickersPopupOpened = false
|
||||
d.stickersPopupOpened = false
|
||||
}
|
||||
}
|
||||
|
||||
property var mentionsPos: []
|
||||
|
||||
function isUploadFilePressed(event) {
|
||||
return (event.key === Qt.Key_U) && (event.modifiers & Qt.ControlModifier) && imageBtn.visible && !imageDialog.visible
|
||||
return (event.key === Qt.Key_U) && (event.modifiers & Qt.ControlModifier) && imageBtn.visible && !imageBtn.highlighted
|
||||
}
|
||||
|
||||
function checkTextInsert() {
|
||||
|
@ -354,7 +343,7 @@ Rectangle {
|
|||
}
|
||||
if (event) {
|
||||
event.accepted = true
|
||||
messageTooLongDialog.open()
|
||||
console.error("Attempting to send a message exceeding length limit")
|
||||
}
|
||||
} else if (event.key === Qt.Key_Escape && control.isReply) {
|
||||
control.isReply = false
|
||||
|
@ -664,8 +653,8 @@ Rectangle {
|
|||
if ((event.modifiers & Qt.ControlModifier) || (event.modifiers & Qt.MetaModifier)) // these are likely shortcuts with no meaningful text
|
||||
return
|
||||
|
||||
if (event.key === Qt.Key_Backspace && textFormatMenu.opened) {
|
||||
textFormatMenu.close()
|
||||
if (event.key === Qt.Key_Backspace && d.textFormatMenu.opened) {
|
||||
d.textFormatMenu.close()
|
||||
}
|
||||
// the text doesn't get registered to the textarea fast enough
|
||||
// we can only get it in the `released` event
|
||||
|
@ -883,19 +872,27 @@ Rectangle {
|
|||
return true;
|
||||
}
|
||||
|
||||
function hideExtendedArea() {
|
||||
function resetImageArea() {
|
||||
isImage = false;
|
||||
isReply = false;
|
||||
control.fileUrlsAndSources = []
|
||||
imageArea.imageSource = [];
|
||||
replyArea.userName = ""
|
||||
replyArea.message = ""
|
||||
for (let i=0; i<validators.children.length; i++) {
|
||||
const validator = validators.children[i]
|
||||
validator.images = []
|
||||
}
|
||||
}
|
||||
|
||||
function resetReplyArea() {
|
||||
isReply = false;
|
||||
replyArea.userName = ""
|
||||
replyArea.message = ""
|
||||
}
|
||||
|
||||
function hideExtendedArea() {
|
||||
resetImageArea()
|
||||
resetReplyArea()
|
||||
}
|
||||
|
||||
function validateImages(imagePaths) {
|
||||
if (!imagePaths || !imagePaths.length) {
|
||||
return []
|
||||
|
@ -912,7 +909,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
function showImageArea(imagePathsOrData) {
|
||||
isImage = true;
|
||||
isImage = imagePathsOrData.length > 0
|
||||
imageArea.imageSource = imagePathsOrData
|
||||
control.fileUrlsAndSources = imageArea.imageSource
|
||||
}
|
||||
|
@ -921,12 +918,8 @@ Rectangle {
|
|||
// Returns true if the images were valid and added
|
||||
function validateImagesAndShowImageArea(imagePaths) {
|
||||
const validImages = validateImages(imagePaths)
|
||||
|
||||
if (validImages.length > 0) {
|
||||
showImageArea(validImages)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
showImageArea(validImages)
|
||||
return isImage
|
||||
}
|
||||
|
||||
function showReplyArea(messageId, userName, message, contentType, image, album, albumCount, sticker) {
|
||||
|
@ -947,7 +940,6 @@ Rectangle {
|
|||
}
|
||||
|
||||
Connections {
|
||||
enabled: control.isActiveChannel
|
||||
target: Global.dragArea
|
||||
ignoreUnknownSignals: true
|
||||
function onDroppedOnValidScreen(drop) {
|
||||
|
@ -963,30 +955,25 @@ Rectangle {
|
|||
messageInputField.forceActiveFocus();
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: imageDialog
|
||||
title: qsTr("Please choose an image")
|
||||
folder: shortcuts.pictures
|
||||
selectMultiple: true
|
||||
nameFilters: [
|
||||
qsTr("Image files (%1)").arg(Constants.acceptedDragNDropImageExtensions.map(img => "*" + img).join(" "))
|
||||
]
|
||||
onAccepted: {
|
||||
imageBtn.highlighted = false
|
||||
validateImagesAndShowImageArea(imageDialog.fileUrls)
|
||||
messageInputField.forceActiveFocus();
|
||||
}
|
||||
onRejected: {
|
||||
imageBtn.highlighted = false
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: imageDialogComponent
|
||||
|
||||
MessageDialog {
|
||||
id: messageTooLongDialog
|
||||
title: qsTr("Your message is too long.")
|
||||
icon: StandardIcon.Critical
|
||||
text: qsTr("Please make your message shorter. We have set the limit to 2000 characters to be courteous of others.")
|
||||
standardButtons: StandardButton.Ok
|
||||
FileDialog {
|
||||
title: qsTr("Please choose an image")
|
||||
folder: shortcuts.pictures
|
||||
selectMultiple: true
|
||||
nameFilters: [
|
||||
qsTr("Image files (%1)").arg(Constants.acceptedDragNDropImageExtensions.map(img => "*" + img).join(" "))
|
||||
]
|
||||
onAccepted: {
|
||||
imageBtn.highlighted = false
|
||||
validateImagesAndShowImageArea(fileUrls)
|
||||
messageInputField.forceActiveFocus()
|
||||
}
|
||||
onRejected: {
|
||||
imageBtn.highlighted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusEmojiSuggestionPopup {
|
||||
|
@ -1026,51 +1013,59 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: chatCommandsPopup
|
||||
id: chatCommandsPopupComponent
|
||||
|
||||
ChatCommandsPopup {
|
||||
x: (parent.width - control.width - Style.current.halfPadding)
|
||||
y: 716
|
||||
id: chatCommandsPopup
|
||||
x: 8
|
||||
y: -height
|
||||
onSendTransactionCommandButtonClicked: {
|
||||
control.sendTransactionCommandButtonClicked();
|
||||
chatCommandsPopup.close();
|
||||
control.sendTransactionCommandButtonClicked()
|
||||
close()
|
||||
}
|
||||
onReceiveTransactionCommandButtonClicked: {
|
||||
control.receiveTransactionCommandButtonClicked();
|
||||
chatCommandsPopup.close();
|
||||
control.receiveTransactionCommandButtonClicked()
|
||||
close()
|
||||
}
|
||||
onClosed: {
|
||||
chatCommandsBtnLoader.highlighted = false;
|
||||
destroy();
|
||||
chatCommandsBtn.highlighted = false
|
||||
destroy()
|
||||
}
|
||||
onOpened: {
|
||||
chatCommandsBtn.highlighted = true
|
||||
}
|
||||
Component.onDestruction: {
|
||||
if (d.chatCommandsPopupOpen)
|
||||
d.chatCommandsPopupOpen = false;
|
||||
}
|
||||
onOpened: {
|
||||
chatCommandsBtnLoader.highlighted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusGifPopup {
|
||||
id: gifPopup
|
||||
width: 360
|
||||
height: 440
|
||||
x: control.width - width - Style.current.halfPadding
|
||||
y: -height
|
||||
gifSelected: function (event, url) {
|
||||
messageInputField.text += "\n" + url
|
||||
control.sendMessage(event)
|
||||
control.isReply = false
|
||||
gifBtn.highlighted = false
|
||||
messageInputField.forceActiveFocus()
|
||||
if (control.closeGifPopupAfterSelection)
|
||||
gifPopup.close()
|
||||
}
|
||||
onClosed: {
|
||||
gifBtn.highlighted = false
|
||||
Component {
|
||||
id: gifPopupComponent
|
||||
|
||||
StatusGifPopup {
|
||||
readonly property point relativePosition: d.getCommonPopupRelativePosition(this, parent)
|
||||
|
||||
width: 360
|
||||
height: 440
|
||||
x: relativePosition.x
|
||||
y: relativePosition.y
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
|
||||
gifSelected: function (event, url) {
|
||||
messageInputField.text += "\n" + url
|
||||
control.sendMessage(event)
|
||||
control.isReply = false
|
||||
messageInputField.forceActiveFocus()
|
||||
if (control.closeGifPopupAfterSelection)
|
||||
close()
|
||||
}
|
||||
onClosed: {
|
||||
gifBtn.popup = null
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1091,12 +1086,12 @@ Rectangle {
|
|||
Layout.bottomMargin: 4
|
||||
icon.name: "chat-commands"
|
||||
type: StatusQ.StatusFlatRoundButton.Type.Tertiary
|
||||
visible: RootStore.isWalletEnabled && !isEdit && control.chatType === Constants.chatType.oneToOne
|
||||
enabled: !control.isContactBlocked
|
||||
onClicked: {
|
||||
d.chatCommandsPopupOpen ? Global.closePopup() : Global.openPopup(chatCommandsPopup);
|
||||
d.chatCommandsPopupOpen = !d.chatCommandsPopupOpen;
|
||||
}
|
||||
visible: RootStore.isWalletEnabled && !isEdit && control.chatType === Constants.chatType.oneToOne
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1112,7 +1107,8 @@ Rectangle {
|
|||
enabled: !control.isContactBlocked
|
||||
onClicked: {
|
||||
highlighted = true
|
||||
imageDialog.open()
|
||||
const popup = imageDialogComponent.createObject(control)
|
||||
popup.open()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1123,7 +1119,6 @@ Rectangle {
|
|||
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
||||
implicitHeight: inputLayout.implicitHeight + inputLayout.anchors.topMargin + inputLayout.anchors.bottomMargin
|
||||
implicitWidth: inputLayout.implicitWidth + inputLayout.anchors.leftMargin + inputLayout.anchors.rightMargin
|
||||
|
||||
|
@ -1131,60 +1126,67 @@ Rectangle {
|
|||
color: isEdit ? Theme.palette.statusChatInput.secondaryBackgroundColor : Style.current.inputBackground
|
||||
radius: 20
|
||||
|
||||
StatusTextFormatMenu {
|
||||
id: textFormatMenu
|
||||
Component {
|
||||
id: textFormatMenuComponent
|
||||
|
||||
StatusChatInputTextFormationAction {
|
||||
wrapper: "**"
|
||||
icon.name: "bold"
|
||||
text: qsTr("Bold")
|
||||
selectedTextWithFormationChars: RootStore.getSelectedTextWithFormationChars(messageInputField)
|
||||
onActionTriggered: checked ?
|
||||
unwrapSelection(wrapper, RootStore.getSelectedTextWithFormationChars(messageInputField)) :
|
||||
wrapSelection(wrapper)
|
||||
}
|
||||
StatusChatInputTextFormationAction {
|
||||
wrapper: "*"
|
||||
icon.name: "italic"
|
||||
text: qsTr("Italic")
|
||||
selectedTextWithFormationChars: RootStore.getSelectedTextWithFormationChars(messageInputField)
|
||||
checked: (surroundedBy("*") && !surroundedBy("**")) || surroundedBy("***")
|
||||
onActionTriggered: checked ?
|
||||
unwrapSelection(wrapper, RootStore.getSelectedTextWithFormationChars(messageInputField)) :
|
||||
wrapSelection(wrapper)
|
||||
}
|
||||
StatusChatInputTextFormationAction {
|
||||
wrapper: "~~"
|
||||
icon.name: "strikethrough"
|
||||
text: qsTr("Strikethrough")
|
||||
selectedTextWithFormationChars: RootStore.getSelectedTextWithFormationChars(messageInputField)
|
||||
onActionTriggered: checked ?
|
||||
unwrapSelection(wrapper, RootStore.getSelectedTextWithFormationChars(messageInputField)) :
|
||||
wrapSelection(wrapper)
|
||||
}
|
||||
StatusChatInputTextFormationAction {
|
||||
wrapper: "`"
|
||||
icon.name: "code"
|
||||
text: qsTr("Code")
|
||||
selectedTextWithFormationChars: RootStore.getSelectedTextWithFormationChars(messageInputField)
|
||||
onActionTriggered: checked ?
|
||||
unwrapSelection(wrapper, RootStore.getSelectedTextWithFormationChars(messageInputField)) :
|
||||
wrapSelection(wrapper)
|
||||
}
|
||||
StatusChatInputTextFormationAction {
|
||||
wrapper: "> "
|
||||
icon.name: "quote"
|
||||
text: qsTr("Quote")
|
||||
checked: messageInputField.selectedText && isSelectedLinePrefixedBy(messageInputField.selectionStart, wrapper)
|
||||
StatusTextFormatMenu {
|
||||
id: textFormatMenu
|
||||
|
||||
onActionTriggered: checked
|
||||
? unprefixSelectedLine(wrapper)
|
||||
: prefixSelectedLine(wrapper)
|
||||
}
|
||||
onClosed: {
|
||||
messageInputField.deselect();
|
||||
onClosed: {
|
||||
messageInputField.deselect()
|
||||
destroy()
|
||||
}
|
||||
|
||||
StatusChatInputTextFormationAction {
|
||||
wrapper: "**"
|
||||
icon.name: "bold"
|
||||
text: qsTr("Bold")
|
||||
selectedTextWithFormationChars: RootStore.getSelectedTextWithFormationChars(messageInputField)
|
||||
onActionTriggered: checked ?
|
||||
unwrapSelection(wrapper, RootStore.getSelectedTextWithFormationChars(messageInputField)) :
|
||||
wrapSelection(wrapper)
|
||||
}
|
||||
StatusChatInputTextFormationAction {
|
||||
wrapper: "*"
|
||||
icon.name: "italic"
|
||||
text: qsTr("Italic")
|
||||
selectedTextWithFormationChars: RootStore.getSelectedTextWithFormationChars(messageInputField)
|
||||
checked: (surroundedBy("*") && !surroundedBy("**")) || surroundedBy("***")
|
||||
onActionTriggered: checked ?
|
||||
unwrapSelection(wrapper, RootStore.getSelectedTextWithFormationChars(messageInputField)) :
|
||||
wrapSelection(wrapper)
|
||||
}
|
||||
StatusChatInputTextFormationAction {
|
||||
wrapper: "~~"
|
||||
icon.name: "strikethrough"
|
||||
text: qsTr("Strikethrough")
|
||||
selectedTextWithFormationChars: RootStore.getSelectedTextWithFormationChars(messageInputField)
|
||||
onActionTriggered: checked ?
|
||||
unwrapSelection(wrapper, RootStore.getSelectedTextWithFormationChars(messageInputField)) :
|
||||
wrapSelection(wrapper)
|
||||
}
|
||||
StatusChatInputTextFormationAction {
|
||||
wrapper: "`"
|
||||
icon.name: "code"
|
||||
text: qsTr("Code")
|
||||
selectedTextWithFormationChars: RootStore.getSelectedTextWithFormationChars(messageInputField)
|
||||
onActionTriggered: checked ?
|
||||
unwrapSelection(wrapper, RootStore.getSelectedTextWithFormationChars(messageInputField)) :
|
||||
wrapSelection(wrapper)
|
||||
}
|
||||
StatusChatInputTextFormationAction {
|
||||
wrapper: "> "
|
||||
icon.name: "quote"
|
||||
text: qsTr("Quote")
|
||||
checked: messageInputField.selectedText && isSelectedLinePrefixedBy(messageInputField.selectionStart, wrapper)
|
||||
|
||||
onActionTriggered: checked
|
||||
? unprefixSelectedLine(wrapper)
|
||||
: prefixSelectedLine(wrapper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: validators
|
||||
anchors.bottom: control.imageErrorMessageLocation === StatusChatInput.ImageErrorMessageLocation.Top ? parent.top : undefined
|
||||
|
@ -1244,8 +1246,7 @@ Rectangle {
|
|||
if (control.fileUrlsAndSources.length > index && control.fileUrlsAndSources[index]) {
|
||||
control.fileUrlsAndSources.splice(index, 1)
|
||||
}
|
||||
isImage = control.fileUrlsAndSources.length > 0
|
||||
validateImages(control.fileUrlsAndSources)
|
||||
showImageArea(control.fileUrlsAndSources)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1359,12 +1360,14 @@ Rectangle {
|
|||
if (messageInputField.selectedText.trim() !== "") {
|
||||
// If it's a double click, just check the mouse position
|
||||
// If it's a mouse select, use the start and end position average)
|
||||
let x = now < messageInputField.lastClick + 500 ? x = event.x :
|
||||
(messageInputField.cursorRectangle.x + event.x) / 2
|
||||
x -= textFormatMenu.width / 2
|
||||
|
||||
textFormatMenu.popup(x, messageInputField.y - textFormatMenu.height - 5)
|
||||
messageInputField.forceActiveFocus();
|
||||
const x = now < messageInputField.lastClick + 500
|
||||
? event.x
|
||||
: (messageInputField.cursorRectangle.x + event.x) / 2
|
||||
let menu = Global.openMenu(textFormatMenuComponent, messageInput, {})
|
||||
menu.x = x - menu.width / 2
|
||||
menu.y = messageInputField.y - menu.height - 5
|
||||
d.textFormatMenu = menu
|
||||
messageInputField.forceActiveFocus()
|
||||
}
|
||||
lastClick = now
|
||||
}
|
||||
|
@ -1466,15 +1469,22 @@ Rectangle {
|
|||
: Theme.palette.baseColor1
|
||||
type: StatusQ.StatusFlatRoundButton.Type.Tertiary
|
||||
color: "transparent"
|
||||
highlighted: d.emojiPopupOpened
|
||||
onClicked: {
|
||||
control.emojiPopupOpened = true
|
||||
|
||||
togglePopup(emojiPopup, emojiBtn)
|
||||
if (d.emojiPopupOpened) {
|
||||
emojiPopup.close()
|
||||
return
|
||||
}
|
||||
d.emojiPopupOpened = true
|
||||
emojiPopup.open()
|
||||
}
|
||||
}
|
||||
|
||||
StatusQ.StatusFlatRoundButton {
|
||||
id: gifBtn
|
||||
|
||||
property Popup popup
|
||||
|
||||
objectName: "gifPopupButton"
|
||||
implicitHeight: 32
|
||||
implicitWidth: 32
|
||||
|
@ -1484,7 +1494,15 @@ Rectangle {
|
|||
: Theme.palette.baseColor1
|
||||
type: StatusQ.StatusFlatRoundButton.Type.Tertiary
|
||||
color: "transparent"
|
||||
onClicked: togglePopup(gifPopup, gifBtn)
|
||||
highlighted: popup && popup.opened
|
||||
onClicked: {
|
||||
if (popup) {
|
||||
popup.close()
|
||||
return
|
||||
}
|
||||
popup = gifPopupComponent.createObject(gifBtn)
|
||||
popup.open()
|
||||
}
|
||||
}
|
||||
|
||||
StatusQ.StatusFlatRoundButton {
|
||||
|
@ -1499,10 +1517,14 @@ Rectangle {
|
|||
type: StatusQ.StatusFlatRoundButton.Type.Tertiary
|
||||
visible: !isEdit && emojiBtn.visible
|
||||
color: "transparent"
|
||||
highlighted: d.stickersPopupOpened
|
||||
onClicked: {
|
||||
control.stickersPopupOpened = true
|
||||
|
||||
togglePopup(control.stickersPopup, stickersBtn)
|
||||
if (d.stickersPopupOpened) {
|
||||
control.stickersPopup.close()
|
||||
return
|
||||
}
|
||||
d.stickersPopupOpened = true
|
||||
control.stickersPopup.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1512,8 +1534,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
StatusQ.StatusButton {
|
||||
id: unblockBtn
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
Layout.fillHeight: true
|
||||
Layout.bottomMargin: 4
|
||||
visible: control.isContactBlocked
|
||||
text: qsTr("Unblock")
|
||||
|
|
|
@ -86,8 +86,6 @@ Row {
|
|||
}
|
||||
onClicked: {
|
||||
imageArea.imageRemoved(index)
|
||||
const tmp = imageArea.imageSource.filter((url, idx) => idx !== index)
|
||||
rptImages.model = tmp
|
||||
}
|
||||
MouseArea {
|
||||
id: buttonMouseArea
|
||||
|
|
|
@ -43,7 +43,6 @@ Popup {
|
|||
|
||||
modal: false
|
||||
width: 360
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
|
||||
background: Rectangle {
|
||||
radius: Style.current.radius
|
||||
|
|
|
@ -8,12 +8,14 @@ import shared.panels 1.0
|
|||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool selected: false
|
||||
property bool useIconInsteadOfImage: false
|
||||
property url source: Style.svg("history")
|
||||
signal clicked
|
||||
height: 24
|
||||
width: 24
|
||||
|
||||
implicitHeight: 24
|
||||
implicitWidth: 24
|
||||
|
||||
RoundedImage {
|
||||
visible: !useIconInsteadOfImage
|
||||
|
@ -25,6 +27,7 @@ Item {
|
|||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
RoundedIcon {
|
||||
id: iconIcon
|
||||
visible: useIconInsteadOfImage
|
||||
|
|
|
@ -280,13 +280,12 @@ Popup {
|
|||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
contentWidth: availableWidth
|
||||
padding: 0
|
||||
contentHeight: availableHeight
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
|
||||
|
||||
RowLayout {
|
||||
width: scrollView.availableWidth
|
||||
height: scrollView.availableHeight
|
||||
spacing: footerContent.spacing
|
||||
|
||||
Repeater {
|
||||
|
@ -298,8 +297,7 @@ Popup {
|
|||
delegate: StatusStickerPackIconWithIndicator {
|
||||
id: packIconWithIndicator
|
||||
visible: installed
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
selected: stickerPackListView.selectedPackId === packId
|
||||
source: thumbnail
|
||||
onClicked: {
|
||||
|
@ -314,6 +312,7 @@ Popup {
|
|||
model: d.stickerPacksLoading ? 7 : 0
|
||||
|
||||
delegate: Rectangle {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
radius: width / 2
|
||||
|
|
|
@ -25,7 +25,6 @@ Loader {
|
|||
property var chatContentModule
|
||||
|
||||
property string channelEmoji
|
||||
property bool isActiveChannel: false
|
||||
|
||||
property var chatLogView
|
||||
property var emojiPopup
|
||||
|
|
Loading…
Reference in New Issue