2023-06-07 13:18:29 +00:00
import QtQuick 2.15
import QtQuick . Controls 2.15
import QtQuick . Layouts 1.15
import QtQml 2.15
2021-07-06 09:42:51 +00:00
import StatusQ . Components 0.1
import StatusQ . Controls 0.1
2024-07-12 09:46:43 +00:00
import StatusQ . Core 0.1
import StatusQ . Core . Backpressure 0.1
import StatusQ . Core . Theme 0.1
2023-06-06 15:42:42 +00:00
import StatusQ . Core . Utils 0.1
2021-07-06 09:42:51 +00:00
2021-10-27 21:27:49 +00:00
import shared 1.0
2024-09-04 14:27:24 +00:00
import shared . controls 1.0
2021-10-27 21:27:49 +00:00
import shared . popups 1.0
2024-09-04 14:27:24 +00:00
import shared . popups . send 1.0
2021-10-27 21:27:49 +00:00
import shared . status 1.0
2024-09-04 14:27:24 +00:00
import shared . stores 1.0 as SharedStores
2021-10-28 20:23:30 +00:00
import shared . views . chat 1.0
2024-09-04 14:27:24 +00:00
import utils 1.0
2023-06-06 15:42:42 +00:00
import SortFilterProxyModel 0.2
2023-06-23 06:17:04 +00:00
import AppLayouts . Communities . popups 1.0
2023-10-17 14:11:53 +00:00
import AppLayouts . Communities . panels 1.0
2024-10-15 09:31:46 +00:00
import AppLayouts . Profile . stores 1.0 as ProfileStores
import AppLayouts . Chat . stores 1.0 as ChatStores
2020-05-25 20:34:26 +00:00
2021-10-01 15:58:36 +00:00
import "../helpers"
import "../controls"
import "../popups"
import "../panels"
import "../../Wallet"
2021-07-16 15:02:47 +00:00
Item {
2021-10-21 22:39:53 +00:00
id: root
2021-12-01 16:47:57 +00:00
// Important: we have parent module in this context only cause qml components
2021-12-09 12:53:40 +00:00
// don't follow struct we have on the backend.
2021-12-01 16:47:57 +00:00
property var parentModule
2024-10-02 20:47:14 +00:00
property SharedStores . RootStore sharedRootStore
2024-10-22 12:39:42 +00:00
property SharedStores . UtilsStore utilsStore
2024-10-15 09:31:46 +00:00
property ChatStores . RootStore rootStore
property ChatStores . CreateChatPropertiesStore createChatPropertiesStore
property ProfileStores . ContactsStore contactsStore
2022-03-07 14:56:05 +00:00
property var emojiPopup
2022-11-14 20:21:00 +00:00
property var stickersPopup
2022-01-04 12:06:05 +00:00
2021-12-16 20:02:58 +00:00
property string activeChatId: parentModule && parentModule . activeItem . id
2022-06-30 19:25:04 +00:00
property int chatsCount: parentModule && parentModule . model ? parentModule.model.count : 0
2023-01-27 15:24:00 +00:00
property int activeChatType: parentModule && parentModule . activeItem . type
2021-12-08 21:20:43 +00:00
property bool stickersLoaded: false
2024-06-03 19:06:34 +00:00
property bool canPost: true
2023-06-08 11:01:01 +00:00
property var viewAndPostHoldingsModel
property bool amISectionAdmin: false
2024-09-17 11:34:24 +00:00
property bool sendViaPersonalChatEnabled
2021-12-08 21:20:43 +00:00
signal openStickerPackPopup ( string stickerPackId )
2021-11-10 08:09:31 +00:00
2022-03-22 08:29:58 +00:00
// This function is called once `1:1` or `group` chat is created.
function checkForCreateChatOptions ( chatId ) {
2023-09-11 10:01:35 +00:00
if ( root . createChatPropertiesStore . createChatStickerHashId !== ""
&& root . createChatPropertiesStore . createChatStickerPackId !== ""
&& root . createChatPropertiesStore . createChatStickerUrl !== "" ) {
root . rootStore . sendSticker (
chatId ,
root . createChatPropertiesStore . createChatStickerHashId ,
"" ,
root . createChatPropertiesStore . createChatStickerPackId ,
root . createChatPropertiesStore . createChatStickerUrl )
} else if ( root . createChatPropertiesStore . createChatInitMessage !== ""
|| root . createChatPropertiesStore . createChatFileUrls . length > 0 ) {
root . rootStore . sendMessage (
chatId , Qt . Key_Enter ,
root . createChatPropertiesStore . createChatInitMessage ,
"" , root . createChatPropertiesStore . createChatFileUrls )
2022-03-22 08:29:58 +00:00
}
2023-05-04 18:36:35 +00:00
root . createChatPropertiesStore . resetProperties ( )
2022-03-22 08:29:58 +00:00
}
2023-06-07 13:18:29 +00:00
QtObject {
id: d
readonly property var activeChatContentModule: d . getChatContentModule ( root . activeChatId )
2024-06-20 22:46:12 +00:00
property bool sendingInProgress: ! ! d . activeChatContentModule ? d.activeChatContentModule.inputAreaModule.sendingInProgress : false
2023-11-07 11:32:21 +00:00
readonly property var urlsList: {
2024-03-20 17:58:46 +00:00
if ( ! d . activeChatContentModule ) {
return
}
2023-11-07 11:32:21 +00:00
urlsModelChangeTracker . revision
ModelUtils . modelToFlatArray ( d . activeChatContentModule . inputAreaModule . urlsModel , "url" )
}
readonly property ModelChangeTracker urlsModelChangeTracker: ModelChangeTracker {
2024-03-20 17:58:46 +00:00
model: ! ! d . activeChatContentModule ? d.activeChatContentModule.inputAreaModule.urlsModel : null
2023-11-07 11:32:21 +00:00
}
2024-10-15 09:31:46 +00:00
readonly property ChatStores . UsersStore activeUsersStore: ChatStores . UsersStore {
2023-06-07 13:18:29 +00:00
usersModule: ! ! d . activeChatContentModule ? d.activeChatContentModule.usersModule : null
2024-10-30 19:02:46 +00:00
chatDetails: ! ! d . activeChatContentModule ? d.activeChatContentModule.chatDetails : null
chatCommunitySectionModule: root . rootStore . chatCommunitySectionModule
2023-06-07 13:18:29 +00:00
}
2024-10-15 09:31:46 +00:00
readonly property ChatStores . MessageStore activeMessagesStore: ChatStores . MessageStore {
2023-06-07 13:18:29 +00:00
messageModule: d . activeChatContentModule ? d.activeChatContentModule.messagesModule : null
chatSectionModule: root . rootStore . chatCommunitySectionModule
}
2023-10-17 08:11:26 +00:00
readonly property string linkPreviewBeginAnchor: ` < a style = "text-decoration:none" href = "#${Constants.appSection.profile}/${Constants.settingsSubsection.messaging}" > `
readonly property string linkPreviewEndAnchor: ` < / a > `
readonly property string linkPreviewEnabledNotification: qsTr ( "Link previews will be shown for all sites. You can manage link previews in %1." , "Go to settings" ) . arg ( linkPreviewBeginAnchor + qsTr ( "Settings" , "Go to settings page" ) + linkPreviewEndAnchor )
readonly property string linkPreviewDisabledNotification: qsTr ( "Link previews will never be shown. You can manage link previews in %1." ) . arg ( linkPreviewBeginAnchor + qsTr ( "Settings" , "Go to settings page" ) + linkPreviewEndAnchor )
readonly property string linkPreviewEnabledForMessageNotification: qsTr ( "Link previews will be shown for this message. You can manage link previews in %1." ) . arg ( linkPreviewBeginAnchor + qsTr ( "Settings" , "Go to settings page" ) + linkPreviewEndAnchor )
2023-06-07 13:18:29 +00:00
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 ( ) {
2024-03-20 17:58:46 +00:00
if ( ! d . activeChatContentModule ) {
return
}
2023-06-07 13:18:29 +00:00
const replyMessageId = d . activeChatContentModule . inputAreaModule . preservedProperties . replyMessageId
if ( replyMessageId )
d . showReplyArea ( replyMessageId )
else
chatInput . resetReplyArea ( )
}
function restoreInputAttachments ( ) {
2024-03-20 17:58:46 +00:00
if ( ! d . activeChatContentModule ) {
return
}
2023-06-07 13:18:29 +00:00
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 )
}
2023-10-09 08:45:16 +00:00
function restoreInputState ( textInput ) {
2023-06-07 13:18:29 +00:00
if ( ! d . activeChatContentModule ) {
2023-10-09 08:45:16 +00:00
chatInput . clear ( )
2023-06-07 13:18:29 +00:00
chatInput . resetReplyArea ( )
chatInput . resetImageArea ( )
return
}
// Restore message text
2023-10-09 08:45:16 +00:00
chatInput . setText ( textInput )
2023-06-07 13:18:29 +00:00
d . restoreInputReply ( )
d . restoreInputAttachments ( )
}
2023-07-11 11:30:55 +00:00
readonly property var updateLinkPreviews: {
2024-03-20 17:58:46 +00:00
if ( ! d . activeChatContentModule ) {
return
}
2023-07-11 11:30:55 +00:00
return Backpressure . debounce ( this , 250 , ( ) = > {
const messageText = root . rootStore . cleanMessageText ( chatInput . textInput . text )
d . activeChatContentModule . inputAreaModule . setText ( messageText )
} )
}
2023-06-07 13:18:29 +00:00
onActiveChatContentModuleChanged: {
2024-03-20 17:58:46 +00:00
if ( ! d . activeChatContentModule ) {
return
2023-10-09 08:45:16 +00:00
}
2024-03-20 17:58:46 +00:00
let preservedText = ""
preservedText = d . activeChatContentModule . inputAreaModule . preservedProperties . text
2023-10-09 08:45:16 +00:00
2023-07-11 11:30:55 +00:00
d . activeChatContentModule . inputAreaModule . clearLinkPreviewCache ( )
2023-06-07 13:18:29 +00:00
// Call later to make sure activeUsersStore and activeMessagesStore bindings are updated
2023-10-09 08:45:16 +00:00
Qt . callLater ( d . restoreInputState , preservedText )
2023-06-07 13:18:29 +00:00
}
2024-12-03 08:36:04 +00:00
function formatBalance ( amount , symbol ) {
let asset = ModelUtils . getByKey ( WalletStore . RootStore . tokensStore . flatTokensModel , "symbol" , symbol )
if ( ! asset )
return "0"
const num = AmountsArithmetic . toNumber ( amount , asset . decimals )
return root . rootStore . currencyStore . formatCurrencyAmount ( num , symbol , { noSynbol: true } )
}
2023-06-07 13:18:29 +00:00
}
2022-01-20 12:15:36 +00:00
EmptyChatPanel {
2022-01-25 15:22:35 +00:00
anchors.fill: parent
2022-06-30 19:25:04 +00:00
visible: root . activeChatId === "" || root . chatsCount == 0
2022-01-20 12:15:36 +00:00
onShareChatKeyClicked: Global . openProfilePopup ( userProfile . pubKey ) ;
}
2021-07-16 15:02:47 +00:00
2022-01-20 12:15:36 +00:00
// 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.
2023-06-07 13:18:29 +00:00
ColumnLayout {
anchors.fill: parent
spacing: 0
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Repeater {
id: chatRepeater
model: parentModule && parentModule . model
2024-03-20 17:58:46 +00:00
Loader {
2023-06-07 13:18:29 +00:00
width: parent . width
height: parent . height
2024-03-20 17:58:46 +00:00
active: model . type !== Constants . chatType . category && model . type !== Constants . chatType . unknown
sourceComponent: ChatContentView {
width: parent . width
height: parent . height
visible: ! root . rootStore . openCreateChat && model . active
chatId: model . itemId
chatType: model . type
chatMessagesLoader.active: model . loaderActive
2024-10-02 20:47:14 +00:00
sharedRootStore: root . sharedRootStore
2024-10-22 12:39:42 +00:00
utilsStore: root . utilsStore
2024-03-20 17:58:46 +00:00
rootStore: root . rootStore
contactsStore: root . contactsStore
2024-12-03 08:36:04 +00:00
formatBalance: d . formatBalance
2024-03-20 17:58:46 +00:00
emojiPopup: root . emojiPopup
stickersPopup: root . stickersPopup
stickersLoaded: root . stickersLoaded
isBlocked: model . blocked
2024-09-17 11:34:24 +00:00
sendViaPersonalChatEnabled: root . sendViaPersonalChatEnabled
2024-12-03 08:36:04 +00:00
areTestNetworksEnabled: root . areTestNetworksEnabled
2024-03-20 17:58:46 +00:00
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 )
}
2023-06-07 13:18:29 +00:00
}
}
}
}
2023-06-14 15:09:46 +00:00
RowLayout {
2023-06-07 13:18:29 +00:00
Layout.fillWidth: true
2024-10-15 19:26:12 +00:00
Layout.margins: Theme . smallPadding
2023-06-06 15:42:42 +00:00
Layout.preferredHeight: chatInputItem . height
2023-06-07 13:18:29 +00:00
2023-06-06 15:42:42 +00:00
Item {
id: chatInputItem
2023-06-14 15:09:46 +00:00
Layout.fillWidth: true
2023-06-06 15:42:42 +00:00
Layout.preferredHeight: chatInput . height
2023-06-07 13:18:29 +00:00
2023-06-06 15:42:42 +00:00
StatusChatInput {
id: chatInput
width: parent . width
visible: ! ! d . activeChatContentModule
2024-09-04 14:27:24 +00:00
2023-06-28 20:19:00 +00:00
// When `enabled` is switched true->false, `textInput.text` is cleared before d.activeChatContentModule updates.
// We delay the binding so that the `inputAreaModule.preservedProperties.text` doesn't get overriden with empty value.
Binding on enabled {
delayed: true
value: ! ! d . activeChatContentModule
&& ! d . activeChatContentModule . chatDetails . blocked
&& root . rootStore . sectionDetails . joined
&& ! root . rootStore . sectionDetails . amIBanned
&& root . rootStore . isUserAllowedToSendMessage
2024-06-20 22:46:12 +00:00
&& ! d . sendingInProgress
2023-06-28 20:19:00 +00:00
}
2023-06-06 15:42:42 +00:00
2024-09-06 13:11:47 +00:00
usersModel: d . activeUsersStore . usersModel
2024-10-02 20:47:14 +00:00
sharedStore: root . sharedRootStore
2024-09-04 14:27:24 +00:00
2024-03-20 17:58:46 +00:00
linkPreviewModel: ! ! d . activeChatContentModule ? d.activeChatContentModule.inputAreaModule.linkPreviewModel : null
2024-12-03 08:36:04 +00:00
paymentRequestModel: ! ! d . activeChatContentModule ? d.activeChatContentModule.inputAreaModule.paymentRequestModel : null
formatBalance: d . formatBalance
2023-11-07 11:32:21 +00:00
urlsList: d . urlsList
2023-10-09 08:45:16 +00:00
askToEnableLinkPreview: {
if ( ! d . activeChatContentModule || ! d . activeChatContentModule . inputAreaModule || ! d . activeChatContentModule . inputAreaModule . preservedProperties )
return false
2023-06-06 15:42:42 +00:00
2023-10-09 08:45:16 +00:00
return d . activeChatContentModule . inputAreaModule . askToEnableLinkPreview
}
2023-06-06 15:42:42 +00:00
textInput.placeholderText: {
if ( ! channelPostRestrictions . visible ) {
2024-03-20 17:58:46 +00:00
if ( d . activeChatContentModule && d . activeChatContentModule . chatDetails . blocked )
2023-06-06 15:42:42 +00:00
return qsTr ( "This user has been blocked." )
2023-06-08 11:01:01 +00:00
if ( ! root . rootStore . sectionDetails . joined || root . rootStore . sectionDetails . amIBanned ) {
2023-06-06 15:42:42 +00:00
return qsTr ( "You need to join this community to send messages" )
2023-06-08 11:01:01 +00:00
}
2024-06-03 19:06:34 +00:00
if ( ! root . canPost ) {
2024-03-04 09:59:52 +00:00
return qsTr ( "Sorry, you don't have permissions to post in this channel." )
2023-06-06 15:42:42 +00:00
}
2024-06-20 22:46:12 +00:00
if ( d . sendingInProgress ) {
return qsTr ( "Sending..." )
}
2023-06-06 15:42:42 +00:00
return root . rootStore . chatInputPlaceHolderText
} else {
return "" ;
}
}
2023-06-07 13:18:29 +00:00
2023-06-06 15:42:42 +00:00
emojiPopup: root . emojiPopup
stickersPopup: root . stickersPopup
chatType: root . activeChatType
2023-06-07 13:18:29 +00:00
2023-06-06 15:42:42 +00:00
textInput.onTextChanged: {
2023-12-06 08:52:05 +00:00
if ( ! ! d . activeChatContentModule && textInput . text !== d . activeChatContentModule . inputAreaModule . preservedProperties . text ) {
2023-06-06 15:42:42 +00:00
d . activeChatContentModule . inputAreaModule . preservedProperties . text = textInput . text
2023-10-17 14:11:53 +00:00
d . updateLinkPreviews ( )
2023-09-26 14:16:53 +00:00
}
2023-06-06 15:42:42 +00:00
}
2023-06-07 13:18:29 +00:00
2023-06-06 15:42:42 +00:00
onReplyMessageIdChanged: {
if ( ! ! d . activeChatContentModule )
d . activeChatContentModule . inputAreaModule . preservedProperties . replyMessageId = replyMessageId
}
2023-06-07 13:18:29 +00:00
2023-06-06 15:42:42 +00:00
onFileUrlsAndSourcesChanged: {
if ( ! ! d . activeChatContentModule )
d . activeChatContentModule . inputAreaModule . preservedProperties . fileUrlsAndSourcesJson = JSON . stringify ( chatInput . fileUrlsAndSources )
2023-06-14 15:09:46 +00:00
}
2023-06-06 15:42:42 +00:00
onStickerSelected: {
2023-06-15 13:58:32 +00:00
root . rootStore . sendSticker ( d . activeChatContentModule . getMyChatId ( ) ,
2023-06-06 15:42:42 +00:00
hashId ,
chatInput . isReply ? chatInput.replyMessageId : "" ,
packId ,
url )
}
2023-06-14 15:09:46 +00:00
2023-06-06 15:42:42 +00:00
onSendMessage: {
if ( ! d . activeChatContentModule ) {
console . debug ( "error on sending message - chat content module is not set" )
return
}
2024-06-20 22:46:12 +00:00
if ( root . rootStore . sendMessage ( d . activeChatContentModule . getMyChatId ( ) ,
event ,
chatInput . getTextWithPublicKeys ( ) ,
chatInput . isReply ? chatInput.replyMessageId : "" ,
chatInput . fileUrlsAndSources
) )
{
Global . playSendMessageSound ( )
chatInput . setText ( "" )
chatInput . textInput . textFormat = TextEdit . PlainText ;
chatInput . textInput . textFormat = TextEdit . RichText ;
}
2023-06-14 15:09:46 +00:00
}
2023-06-06 15:42:42 +00:00
onKeyUpPress: {
2024-10-15 09:31:46 +00:00
d . activeMessagesStore . setEditModeOnLastMessage ( root . contactsStore . myPublicKey )
2023-06-14 15:09:46 +00:00
}
2024-09-04 14:27:24 +00:00
2023-09-26 14:16:53 +00:00
onLinkPreviewReloaded: ( link ) = > d . activeChatContentModule . inputAreaModule . reloadLinkPreview ( link )
2023-10-17 08:11:26 +00:00
onEnableLinkPreview: ( ) = > {
d . activeChatContentModule . inputAreaModule . enableLinkPreview ( )
Global . displayToastMessage ( d . linkPreviewEnabledNotification , "" , "show" , false , Constants . ephemeralNotificationType . success , "" )
}
onDisableLinkPreview: ( ) = > {
d . activeChatContentModule . inputAreaModule . disableLinkPreview ( )
Global . displayToastMessage ( d . linkPreviewDisabledNotification , "" , "hide" , false , Constants . ephemeralNotificationType . danger , "" )
}
onEnableLinkPreviewForThisMessage: ( ) = > {
d . activeChatContentModule . inputAreaModule . setLinkPreviewEnabledForCurrentMessage ( true )
Global . displayToastMessage ( d . linkPreviewEnabledForMessageNotification , "" , "show" , false , Constants . ephemeralNotificationType . success , "" )
}
onDismissLinkPreviewSettings: ( ) = > {
d . activeChatContentModule . inputAreaModule . setLinkPreviewEnabledForCurrentMessage ( false )
}
2023-10-09 08:45:16 +00:00
onDismissLinkPreview: ( index ) = > d . activeChatContentModule . inputAreaModule . removeLinkPreviewData ( index )
2024-12-03 08:36:04 +00:00
onRemovePaymentRequestPreview: ( index ) = > d . activeChatContentModule . inputAreaModule . removePaymentRequestPreviewData ( index )
2023-06-14 15:09:46 +00:00
}
2023-06-06 15:42:42 +00:00
ChatPermissionQualificationPanel {
id: channelPostRestrictions
width: chatInput . textInput . width
height: chatInput . textInput . height
anchors.left: parent . left
2024-10-15 19:26:12 +00:00
anchors.leftMargin: ( 2 * Theme . bigPadding )
2023-06-08 11:01:01 +00:00
visible: ( ! ! root . viewAndPostHoldingsModel && ( root . viewAndPostHoldingsModel . count > 0 )
2024-06-03 19:06:34 +00:00
&& ! root . amISectionAdmin && ! root . canPost )
2023-06-06 15:42:42 +00:00
assetsModel: root . rootStore . assetsModel
collectiblesModel: root . rootStore . collectiblesModel
holdingsModel: root . viewAndPostHoldingsModel
2023-06-14 15:09:46 +00:00
}
2023-06-07 13:18:29 +00:00
}
2023-06-14 15:09:46 +00:00
StatusButton {
Layout.fillHeight: true
Layout.maximumHeight: chatInput . implicitHeight
verticalPadding: 0
2023-06-15 13:58:32 +00:00
visible: ! ! d . activeChatContentModule && d . activeChatContentModule . chatDetails . blocked
2023-06-14 15:09:46 +00:00
text: qsTr ( "Unblock" )
type: StatusBaseButton . Type . Danger
onClicked: {
2023-06-15 13:58:32 +00:00
if ( ! ! d . activeChatContentModule )
d . activeChatContentModule . unblockChat ( )
2023-06-14 15:09:46 +00:00
}
2021-09-21 12:26:32 +00:00
}
2021-12-09 12:53:40 +00:00
}
}
2020-05-25 20:34:26 +00:00
}