From 2f534cc327c576c14873eccdd852d35f4fc4209f Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Mon, 16 Aug 2021 17:06:11 +0200 Subject: [PATCH] Fix file message menu and refactoring Chat Menu --- linphone-app/resources.qrc | 2 + .../ParticipantImdnStateProxyModel.cpp | 16 +- .../modules/Linphone/Chat/ChatDeliveries.qml | 62 ++ .../ui/modules/Linphone/Chat/ChatMenu.qml | 93 +++ .../ui/modules/Linphone/Chat/FileMessage.qml | 696 +++++++++--------- .../ui/modules/Linphone/Chat/Message.qml | 100 +-- 6 files changed, 533 insertions(+), 436 deletions(-) create mode 100644 linphone-app/ui/modules/Linphone/Chat/ChatDeliveries.qml create mode 100644 linphone-app/ui/modules/Linphone/Chat/ChatMenu.qml diff --git a/linphone-app/resources.qrc b/linphone-app/resources.qrc index 0b490187b..f96bda4cc 100644 --- a/linphone-app/resources.qrc +++ b/linphone-app/resources.qrc @@ -390,6 +390,8 @@ ui/modules/Linphone/Calls/ConferenceControls.qml ui/modules/Linphone/Chat/Chat.js ui/modules/Linphone/Chat/Chat.qml + ui/modules/Linphone/Chat/ChatDeliveries.qml + ui/modules/Linphone/Chat/ChatMenu.qml ui/modules/Linphone/Chat/Event.qml ui/modules/Linphone/Chat/FileMessage.qml ui/modules/Linphone/Chat/IncomingMessage.qml diff --git a/linphone-app/src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp b/linphone-app/src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp index 302a1354f..f6112ad84 100644 --- a/linphone-app/src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp +++ b/linphone-app/src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp @@ -61,12 +61,14 @@ ChatMessageModel * ParticipantImdnStateProxyModel::getChatMessageModel(){ } void ParticipantImdnStateProxyModel::setChatMessageModel(ChatMessageModel * message){ - ParticipantImdnStateListModel *model = static_cast(sourceModel()); - ParticipantImdnStateListModel *messageModel = message->getParticipantImdnStates().get(); - if( model != messageModel){ - setSourceModel(messageModel); - connect(messageModel, &ParticipantImdnStateListModel::countChanged, this, &ParticipantImdnStateProxyModel::countChanged); - sort(0); - emit chatMessageModelChanged(); + if(message){ + ParticipantImdnStateListModel *model = static_cast(sourceModel()); + ParticipantImdnStateListModel *messageModel = message->getParticipantImdnStates().get(); + if( model != messageModel){ + setSourceModel(messageModel); + connect(messageModel, &ParticipantImdnStateListModel::countChanged, this, &ParticipantImdnStateProxyModel::countChanged); + sort(0); + emit chatMessageModelChanged(); + } } } \ No newline at end of file diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatDeliveries.qml b/linphone-app/ui/modules/Linphone/Chat/ChatDeliveries.qml new file mode 100644 index 000000000..3477ef37a --- /dev/null +++ b/linphone-app/ui/modules/Linphone/Chat/ChatDeliveries.qml @@ -0,0 +1,62 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 + +import Clipboard 1.0 +import Common 1.0 +import Linphone 1.0 + +import Common.Styles 1.0 +import Linphone.Styles 1.0 +import TextToSpeech 1.0 +import Utils 1.0 +import Units 1.0 +import UtilsCpp 1.0 +import LinphoneEnums 1.0 + +import 'Message.js' as Logic + +// ============================================================================= + + +GridView{ + id: deliveryLayout + + property ChatMessageModel chatMessageModel + + + //height: visible ? ChatStyle.composingText.height*container.proxyModel.composers.length : 0 + height: visible ? (ChatStyle.composingText.height-5)*deliveryLayout.model.count : 0 + cellWidth: parent.width; cellHeight: ChatStyle.composingText.height-5 + visible:false + model: ParticipantImdnStateProxyModel{ + id: imdnStatesModel + chatMessageModel: deliveryLayout.chatMessageModel + } + function getText(state, displayName, stateChangeTime){ + if(state == LinphoneEnums.ChatMessageStateDelivered) + //: 'Send to %1 - %2' Little message to indicate the state of a message + //~ Context %1 is someone, %2 is a date/time. The state is that the message has been sent but not received. + return qsTr('deliveryDelivered').arg(displayName).arg(stateChangeTime) + else if(state == LinphoneEnums.ChatMessageStateDeliveredToUser) + //: 'Retrieved by %1 - %2' Little message to indicate the state of a message + //~ Context %1 is someone, %2 is a date/time. The state is that the message has been retrieved + return qsTr('deliveryDeliveredToUser').arg(displayName).arg(stateChangeTime) + else if(state == LinphoneEnums.ChatMessageStateDisplayed) + //: 'Read by %1 - %2' Little message to indicate the state of a message + //~ Context %1 is someone, %2 is a date/time. The state that the message has been read. + return qsTr('deliveryDisplayed').arg(displayName).arg(stateChangeTime) + else if(state == LinphoneEnums.ChatMessageStateNotDelivered) + //: "%1 have nothing received" Little message to indicate the state of a message + //~ Context %1 is someone. The state is that the message hasn't been delivered. + return qsTr('deliveryNotDelivered').arg(displayName) + else return '' + } + delegate:Text{ + height: ChatStyle.composingText.height-5 + width: GridView.width + text: deliveryLayout.getText(modelData.state, modelData.displayName, UtilsCpp.toDateTimeString(modelData.stateChangeTime)) + color: "#B1B1B1" + font.pointSize: Units.dp * 8 + elide: Text.ElideMiddle + } +} diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatMenu.qml b/linphone-app/ui/modules/Linphone/Chat/ChatMenu.qml new file mode 100644 index 000000000..c350f7915 --- /dev/null +++ b/linphone-app/ui/modules/Linphone/Chat/ChatMenu.qml @@ -0,0 +1,93 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 + +import Clipboard 1.0 +import Common 1.0 +import Linphone 1.0 + +import Common.Styles 1.0 +import Linphone.Styles 1.0 +import TextToSpeech 1.0 +import Utils 1.0 +import Units 1.0 +import UtilsCpp 1.0 +import LinphoneEnums 1.0 + +import 'Message.js' as Logic + +// ============================================================================= + +Item { + id: container + property string lastTextSelected + property string content + property int deliveryCount : 0 + + signal deliveryStatusClecked() + signal removeEntry() + + + Menu { + id: messageMenu + menuStyle : MenuStyle.aux + MenuItem { + //: 'Copy all' : Text menu to copy all message text into clipboard + text: (container.lastTextSelected == '' ? qsTr('menuCopyAll') + //: 'Copy' : Text menu to copy selected text in message into clipboard + : qsTr('menuCopy')) + iconMenu: 'menu_copy_text' + iconSizeMenu: 17 + iconLayoutDirection: Qt.RightToLeft + menuItemStyle : MenuItemStyle.aux + onTriggered: Clipboard.text = (container.lastTextSelected == '' ? container.content : container.lastTextSelected) + visible: content != '' + } + + MenuItem { + enabled: TextToSpeech.available + text: qsTr('menuPlayMe') + iconMenu: 'speaker' + iconSizeMenu: 17 + iconLayoutDirection: Qt.RightToLeft + menuItemStyle : MenuItemStyle.aux + onTriggered: TextToSpeech.say(container.content) + visible: content != '' + } + MenuItem { + //: 'Delivery status' : Item menu that lead to IMDN of a message + text: qsTr('menuDeliveryStatus') + iconMenu: 'menu_imdn_info' + iconSizeMenu: 17 + iconLayoutDirection: Qt.RightToLeft + menuItemStyle : MenuItemStyle.aux + visible: container.deliveryCount > 0 + onTriggered: container.deliveryStatusClecked() + } + MenuItem { + //: 'Delete' : Item menu to delete a message + text: qsTr('menuDelete') + iconMenu: 'menu_delete' + iconSizeMenu: 17 + iconLayoutDirection: Qt.RightToLeft + menuItemStyle : MenuItemStyle.auxRed + onTriggered: removeEntry() + } + } + + + + // Handle hovered link. + MouseArea { + anchors.fill:parent + // height: parent.height + // width: rectangle.width + + acceptedButtons: Qt.RightButton + cursorShape: parent.hoveredLink + ? Qt.PointingHandCursor + : Qt.IBeamCursor + + onClicked: mouse.button === Qt.RightButton && messageMenu.open() + } + +} diff --git a/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml b/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml index 8135816e9..da09fccde 100644 --- a/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml @@ -13,343 +13,365 @@ import Utils 1.0 Row { id:mainRow - // --------------------------------------------------------------------------- - // Avatar if it's an incoming message. - // --------------------------------------------------------------------------- - - property bool isOutgoing : $chatEntry.isOutgoing || $chatEntry.state == LinphoneEnums.ChatMessageStateIdle; + // --------------------------------------------------------------------------- + // Avatar if it's an incoming message. + // --------------------------------------------------------------------------- - Item { - height: ChatStyle.entry.lineHeight - width: ChatStyle.entry.metaWidth - - Component { - id: avatar - - Avatar { - height: ChatStyle.entry.message.incoming.avatarSize - width: ChatStyle.entry.message.incoming.avatarSize - - image: $chatEntry.contactModel? $chatEntry.contactModel.vcard.avatar : '' //chat.sipAddressObserver.contact ? chat.sipAddressObserver.contact.vcard.avatar : '' - username: isOutgoing? $chatEntry.toDisplayName : $chatEntry.fromDisplayName - - TooltipArea{ - delay:0 - text:parent.username+'\n'+ (isOutgoing ? $chatEntry.toSipAddress : $chatEntry.fromSipAddress) - tooltipParent:mainRow + property bool isOutgoing : $chatEntry.isOutgoing || $chatEntry.state == LinphoneEnums.ChatMessageStateIdle; + + Item { + height: ChatStyle.entry.lineHeight + width: ChatStyle.entry.metaWidth + + Component { + id: avatar + + Avatar { + height: ChatStyle.entry.message.incoming.avatarSize + width: ChatStyle.entry.message.incoming.avatarSize + + image: $chatEntry.contactModel? $chatEntry.contactModel.vcard.avatar : '' //chat.sipAddressObserver.contact ? chat.sipAddressObserver.contact.vcard.avatar : '' + username: isOutgoing? $chatEntry.toDisplayName : $chatEntry.fromDisplayName + + TooltipArea{ + delay:0 + text:parent.username+'\n'+ (isOutgoing ? $chatEntry.toSipAddress : $chatEntry.fromSipAddress) + tooltipParent:mainRow + } + } } - } - } - - Loader { - anchors.centerIn: parent - sourceComponent: !isOutgoing? avatar : undefined - } - } - - // --------------------------------------------------------------------------- - // File message. - // --------------------------------------------------------------------------- - - Row { - spacing: ChatStyle.entry.message.extraContent.leftMargin - - Rectangle { - id: rectangle - - readonly property bool isError: Utils.includes([ - LinphoneEnums.ChatMessageStateFileTransferError, - LinphoneEnums.ChatMessageStateNotDelivered, - ], $chatEntry.state) - readonly property bool isUploaded: $chatEntry.state == LinphoneEnums.ChatMessageStateDelivered - readonly property bool isDelivered: $chatEntry.state == LinphoneEnums.ChatMessageStateDeliveredToUser - readonly property bool isRead: $chatEntry.state == LinphoneEnums.ChatMessageStateDisplayed - - - //property ContentModel contentModel : ($chatEntry.getContent(0) ? $chatEntry.getContent(0) : null) - property ContentModel contentModel : $chatEntry.fileContentModel - property string thumbnail : contentModel.thumbnail - color: isOutgoing - ? ChatStyle.entry.message.outgoing.backgroundColor - : ChatStyle.entry.message.incoming.backgroundColor - - height: ChatStyle.entry.message.file.height - width: ChatStyle.entry.message.file.width - - radius: ChatStyle.entry.message.radius - - RowLayout { - anchors { - fill: parent - margins: ChatStyle.entry.message.file.margins - } - - spacing: ChatStyle.entry.message.file.spacing - - - // --------------------------------------------------------------------- - // Thumbnail or extension. - // --------------------------------------------------------------------- - - Component { - id: thumbnailImage - - Image { - mipmap: Qt.platform.os === 'osx' - source: rectangle.contentModel.thumbnail - } - } - - Component { - id: extension - - Rectangle { - color: ChatStyle.entry.message.file.extension.background.color - - Text { - anchors.fill: parent - - color: ChatStyle.entry.message.file.extension.text.color - font.bold: true - elide: Text.ElideRight - text: (rectangle.contentModel?Utils.getExtension(rectangle.contentModel.name).toUpperCase():'') - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - } - - Loader { - id: thumbnailProvider - - Layout.fillHeight: true - Layout.preferredWidth: parent.height - - sourceComponent: (rectangle.contentModel ? (rectangle.contentModel.thumbnail ? thumbnailImage : extension ): undefined) - - ScaleAnimator { - id: thumbnailProviderAnimator - - target: thumbnailProvider - - duration: ChatStyle.entry.message.file.animation.duration - easing.type: Easing.InOutQuad - from: 1.0 - } - - states: State { - name: 'hovered' - } - - transitions: [ - Transition { - from: '' - to: 'hovered' - - ScriptAction { - script: { - if (thumbnailProviderAnimator.running) { - thumbnailProviderAnimator.running = false - } - - thumbnailProvider.z = Constants.zPopup - thumbnailProviderAnimator.to = ChatStyle.entry.message.file.animation.to - thumbnailProviderAnimator.running = true - } - } - }, - Transition { - from: 'hovered' - to: '' - - ScriptAction { - script: { - if (thumbnailProviderAnimator.running) { - thumbnailProviderAnimator.running = false - } - - thumbnailProviderAnimator.to = 1.0 - thumbnailProviderAnimator.running = true - thumbnailProvider.z = 0 - } - } - } - ] - } - - // --------------------------------------------------------------------- - // Upload or file status. - // --------------------------------------------------------------------- - - Column { - Layout.fillWidth: true - Layout.fillHeight: true - - spacing: ChatStyle.entry.message.file.status.spacing - - Text { - id: fileName - - color: isOutgoing - ? ChatStyle.entry.message.outgoing.text.color - : ChatStyle.entry.message.incoming.text.color - elide: Text.ElideRight - - font { - bold: true - pointSize: isOutgoing - ? ChatStyle.entry.message.outgoing.text.pointSize - : ChatStyle.entry.message.incoming.text.pointSize - } - - text: (rectangle.contentModel ? rectangle.contentModel.name : '') - width: parent.width - } - - ProgressBar { - id: progressBar - - height: ChatStyle.entry.message.file.status.bar.height - width: parent.width - - to: (rectangle.contentModel ? rectangle.contentModel.fileSize : 0) - value: rectangle.contentModel ? rectangle.contentModel.fileOffset || 0 : 0 - visible: $chatEntry.state == LinphoneEnums.ChatMessageStateInProgress || $chatEntry.state == LinphoneEnums.ChatMessageStateFileTransferInProgress - background: Rectangle { - color: ChatStyle.entry.message.file.status.bar.background.color - radius: ChatStyle.entry.message.file.status.bar.radius - } - - contentItem: Item { - Rectangle { - color: ChatStyle.entry.message.file.status.bar.contentItem.color - height: parent.height - width: progressBar.visualPosition * parent.width - - radius: ChatStyle.entry.message.file.status.bar.radius - } - } - } - - Text { - color: fileName.color - elide: Text.ElideRight - font.pointSize: fileName.font.pointSize - text: { - if(rectangle.contentModel){ - var fileSize = Utils.formatSize(rectangle.contentModel.fileSize) - return progressBar.visible - ? Utils.formatSize(rectangle.contentModel.fileOffset) + '/' + fileSize - : fileSize - }else - return '' - } - } - } - } - - Icon { - anchors { - bottom: parent.bottom - bottomMargin: ChatStyle.entry.message.file.margins - right: parent.right - rightMargin: ChatStyle.entry.message.file.margins - } - - icon: 'download' - iconSize: ChatStyle.entry.message.file.iconSize - visible: (rectangle.contentModel?!isOutgoing&& !rectangle.contentModel.wasDownloaded : false) - } - - MouseArea { - function handleMouseMove (mouse) { - thumbnailProvider.state = Utils.pointIsInItem(this, thumbnailProvider, mouse) - ? 'hovered' - : '' - } - - anchors.fill: parent - visible: ((rectangle.isUploaded || rectangle.isRead) && !isOutgoing) || isOutgoing - - onClicked: { - if (Utils.pointIsInItem(this, thumbnailProvider, mouse)) { - rectangle.contentModel.openFile() - } else if (rectangle.contentModel && rectangle.contentModel.wasDownloaded) { - rectangle.contentModel.openFile(true)// Show directory - } else { - rectangle.contentModel.downloadFile() - } - } - - onExited: thumbnailProvider.state = '' - onMouseXChanged: handleMouseMove.call(this, mouse) - onMouseYChanged: handleMouseMove.call(this, mouse) - } - } - - // ------------------------------------------------------------------------- - // Resend/Remove file message. - // ------------------------------------------------------------------------- - - Row { - spacing: ChatStyle.entry.message.extraContent.spacing - - Component { - id: icon - - Icon { - anchors.centerIn: parent - - icon: rectangle.isError ? 'chat_error' : - (rectangle.isRead ? 'chat_read' : - (rectangle.isDelivered ? 'chat_delivered' : '')) - - iconSize: ChatStyle.entry.message.outgoing.sendIconSize - - MouseArea { - anchors.fill: parent - visible: (rectangle.isError || $chatEntry.state == LinphoneEnums.ChatMessageStateIdle) && isOutgoing - onClicked: proxyModel.resendMessage(index) - } - } - } - - Component { - id: indicator - - Item { - anchors.fill: parent - - BusyIndicator { - anchors.centerIn: parent - - height: ChatStyle.entry.message.outgoing.busyIndicatorSize - width: ChatStyle.entry.message.outgoing.busyIndicatorSize - } - } - } - - Loader { - height: ChatStyle.entry.lineHeight - width: ChatStyle.entry.message.outgoing.areaSize - - sourceComponent: isOutgoing - ? ( - $chatEntry.state == LinphoneEnums.ChatMessageStateInProgress || $chatEntry.state == LinphoneEnums.ChatMessageStateFileTransferInProgress - ? indicator - : icon - ) : undefined - } - - ActionButton { - height: ChatStyle.entry.lineHeight - icon: 'delete' - iconSize: ChatStyle.entry.deleteIconSize - visible: isHoverEntry() - - onClicked: removeEntry() - } - } - } + + Loader { + anchors.centerIn: parent + sourceComponent: !isOutgoing? avatar : undefined + } + } + + // --------------------------------------------------------------------------- + // File message. + // --------------------------------------------------------------------------- + + Row { + spacing: ChatStyle.entry.message.extraContent.leftMargin + Item{ + width: ChatStyle.entry.message.file.width + height:rectangle.height + deliveryLayout.height + + Rectangle { + id: rectangle + + readonly property bool isError: Utils.includes([ + LinphoneEnums.ChatMessageStateFileTransferError, + LinphoneEnums.ChatMessageStateNotDelivered, + ], $chatEntry.state) + readonly property bool isUploaded: $chatEntry.state == LinphoneEnums.ChatMessageStateDelivered + readonly property bool isDelivered: $chatEntry.state == LinphoneEnums.ChatMessageStateDeliveredToUser + readonly property bool isRead: $chatEntry.state == LinphoneEnums.ChatMessageStateDisplayed + + + //property ContentModel contentModel : ($chatEntry.getContent(0) ? $chatEntry.getContent(0) : null) + property ContentModel contentModel : $chatEntry.fileContentModel + property string thumbnail : contentModel.thumbnail + color: isOutgoing + ? ChatStyle.entry.message.outgoing.backgroundColor + : ChatStyle.entry.message.incoming.backgroundColor + + height: ChatStyle.entry.message.file.height + width: ChatStyle.entry.message.file.width + + radius: ChatStyle.entry.message.radius + + RowLayout { + anchors { + fill: parent + margins: ChatStyle.entry.message.file.margins + } + + spacing: ChatStyle.entry.message.file.spacing + + + // --------------------------------------------------------------------- + // Thumbnail or extension. + // --------------------------------------------------------------------- + + Component { + id: thumbnailImage + + Image { + mipmap: Qt.platform.os === 'osx' + source: rectangle.contentModel.thumbnail + } + } + + Component { + id: extension + + Rectangle { + color: ChatStyle.entry.message.file.extension.background.color + + Text { + anchors.fill: parent + + color: ChatStyle.entry.message.file.extension.text.color + font.bold: true + elide: Text.ElideRight + text: (rectangle.contentModel?Utils.getExtension(rectangle.contentModel.name).toUpperCase():'') + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + } + + Loader { + id: thumbnailProvider + + Layout.fillHeight: true + Layout.preferredWidth: parent.height + + sourceComponent: (rectangle.contentModel ? (rectangle.contentModel.thumbnail ? thumbnailImage : extension ): undefined) + + ScaleAnimator { + id: thumbnailProviderAnimator + + target: thumbnailProvider + + duration: ChatStyle.entry.message.file.animation.duration + easing.type: Easing.InOutQuad + from: 1.0 + } + + states: State { + name: 'hovered' + } + + transitions: [ + Transition { + from: '' + to: 'hovered' + + ScriptAction { + script: { + if (thumbnailProviderAnimator.running) { + thumbnailProviderAnimator.running = false + } + + thumbnailProvider.z = Constants.zPopup + thumbnailProviderAnimator.to = ChatStyle.entry.message.file.animation.to + thumbnailProviderAnimator.running = true + } + } + }, + Transition { + from: 'hovered' + to: '' + + ScriptAction { + script: { + if (thumbnailProviderAnimator.running) { + thumbnailProviderAnimator.running = false + } + + thumbnailProviderAnimator.to = 1.0 + thumbnailProviderAnimator.running = true + thumbnailProvider.z = 0 + } + } + } + ] + } + + // --------------------------------------------------------------------- + // Upload or file status. + // --------------------------------------------------------------------- + + Column { + Layout.fillWidth: true + Layout.fillHeight: true + + spacing: ChatStyle.entry.message.file.status.spacing + + Text { + id: fileName + + color: isOutgoing + ? ChatStyle.entry.message.outgoing.text.color + : ChatStyle.entry.message.incoming.text.color + elide: Text.ElideRight + + font { + bold: true + pointSize: isOutgoing + ? ChatStyle.entry.message.outgoing.text.pointSize + : ChatStyle.entry.message.incoming.text.pointSize + } + + text: (rectangle.contentModel ? rectangle.contentModel.name : '') + width: parent.width + } + + ProgressBar { + id: progressBar + + height: ChatStyle.entry.message.file.status.bar.height + width: parent.width + + to: (rectangle.contentModel ? rectangle.contentModel.fileSize : 0) + value: rectangle.contentModel ? rectangle.contentModel.fileOffset || 0 : 0 + visible: $chatEntry.state == LinphoneEnums.ChatMessageStateInProgress || $chatEntry.state == LinphoneEnums.ChatMessageStateFileTransferInProgress + background: Rectangle { + color: ChatStyle.entry.message.file.status.bar.background.color + radius: ChatStyle.entry.message.file.status.bar.radius + } + + contentItem: Item { + Rectangle { + color: ChatStyle.entry.message.file.status.bar.contentItem.color + height: parent.height + width: progressBar.visualPosition * parent.width + + radius: ChatStyle.entry.message.file.status.bar.radius + } + } + } + + Text { + color: fileName.color + elide: Text.ElideRight + font.pointSize: fileName.font.pointSize + text: { + if(rectangle.contentModel){ + var fileSize = Utils.formatSize(rectangle.contentModel.fileSize) + return progressBar.visible + ? Utils.formatSize(rectangle.contentModel.fileOffset) + '/' + fileSize + : fileSize + }else + return '' + } + } + } + } + + Icon { + anchors { + bottom: parent.bottom + bottomMargin: ChatStyle.entry.message.file.margins + right: parent.right + rightMargin: ChatStyle.entry.message.file.margins + } + + icon: 'download' + iconSize: ChatStyle.entry.message.file.iconSize + visible: (rectangle.contentModel?!isOutgoing&& !rectangle.contentModel.wasDownloaded : false) + } + + MouseArea { + function handleMouseMove (mouse) { + thumbnailProvider.state = Utils.pointIsInItem(this, thumbnailProvider, mouse) + ? 'hovered' + : '' + } + + anchors.fill: parent + visible: ((rectangle.isUploaded || rectangle.isRead) && !isOutgoing) || isOutgoing + + onClicked: { + if (Utils.pointIsInItem(this, thumbnailProvider, mouse)) { + rectangle.contentModel.openFile() + } else if (rectangle.contentModel && rectangle.contentModel.wasDownloaded) { + rectangle.contentModel.openFile(true)// Show directory + } else { + rectangle.contentModel.downloadFile() + } + } + + onExited: thumbnailProvider.state = '' + onMouseXChanged: handleMouseMove.call(this, mouse) + onMouseYChanged: handleMouseMove.call(this, mouse) + } + ChatMenu{ + height: parent.height + width: rectangle.width + + deliveryCount: deliveryLayout.model.count + onDeliveryStatusClecked: deliveryLayout.visible = !deliveryLayout.visible + } + } + + ChatDeliveries{ + id: deliveryLayout + anchors.top:rectangle.bottom + anchors.left:parent.left + anchors.right:parent.right + anchors.rightMargin: 50 + + chatMessageModel: $chatEntry + } + } + + // ------------------------------------------------------------------------- + // Resend/Remove file message. + // ------------------------------------------------------------------------- + + Row { + spacing: ChatStyle.entry.message.extraContent.spacing + + Component { + id: icon + + Icon { + anchors.centerIn: parent + + icon: rectangle.isError ? 'chat_error' : + (rectangle.isRead ? 'chat_read' : + (rectangle.isDelivered ? 'chat_delivered' : '')) + + iconSize: ChatStyle.entry.message.outgoing.sendIconSize + + MouseArea { + anchors.fill: parent + visible: (rectangle.isError || $chatEntry.state == LinphoneEnums.ChatMessageStateIdle) && isOutgoing + onClicked: proxyModel.resendMessage(index) + } + } + } + + Component { + id: indicator + + Item { + anchors.fill: parent + + BusyIndicator { + anchors.centerIn: parent + + height: ChatStyle.entry.message.outgoing.busyIndicatorSize + width: ChatStyle.entry.message.outgoing.busyIndicatorSize + } + } + } + + Loader { + height: ChatStyle.entry.lineHeight + width: ChatStyle.entry.message.outgoing.areaSize + + sourceComponent: isOutgoing + ? ( + $chatEntry.state == LinphoneEnums.ChatMessageStateInProgress || $chatEntry.state == LinphoneEnums.ChatMessageStateFileTransferInProgress + ? indicator + : icon + ) : undefined + } + + ActionButton { + height: ChatStyle.entry.lineHeight + icon: 'delete' + iconSize: ChatStyle.entry.deleteIconSize + visible: isHoverEntry() + + onClicked: removeEntry() + } + } + } + } diff --git a/linphone-app/ui/modules/Linphone/Chat/Message.qml b/linphone-app/ui/modules/Linphone/Chat/Message.qml index 1bd4853bb..ad7c62c59 100644 --- a/linphone-app/ui/modules/Linphone/Chat/Message.qml +++ b/linphone-app/ui/modules/Linphone/Chat/Message.qml @@ -109,65 +109,14 @@ Item { deselect() } - - Menu { - id: messageMenu - menuStyle : MenuStyle.aux - MenuItem { - //: 'Copy all' : Text menu to copy all message text into clipboard - text: (message.lastTextSelected == '' ? qsTr('menuCopyAll') - //: 'Copy' : Text menu to copy selected text in message into clipboard - : qsTr('menuCopy')) - iconMenu: 'menu_copy_text' - iconSizeMenu: 17 - iconLayoutDirection: Qt.RightToLeft - menuItemStyle : MenuItemStyle.aux - onTriggered: Clipboard.text = (message.lastTextSelected == '' ? $chatEntry.content : message.lastTextSelected) - } - - MenuItem { - enabled: TextToSpeech.available - text: qsTr('menuPlayMe') - iconMenu: 'speaker' - iconSizeMenu: 17 - iconLayoutDirection: Qt.RightToLeft - menuItemStyle : MenuItemStyle.aux - onTriggered: TextToSpeech.say($chatEntry.content) - } - MenuItem { - //: 'Delivery status' : Item menu that lead to IMDN of a message - text: qsTr('menuDeliveryStatus') - iconMenu: 'menu_imdn_info' - iconSizeMenu: 17 - iconLayoutDirection: Qt.RightToLeft - menuItemStyle : MenuItemStyle.aux - visible: deliveryLayout.model.count > 0 - onTriggered: deliveryLayout.visible = !deliveryLayout.visible - } - MenuItem { - //: 'Delete' : Item menu to delete a message - text: qsTr('menuDelete') - iconMenu: 'menu_delete' - iconSizeMenu: 17 - iconLayoutDirection: Qt.RightToLeft - menuItemStyle : MenuItemStyle.auxRed - onTriggered: removeEntry() - } - } - - - - // Handle hovered link. - MouseArea { + ChatMenu{ height: parent.height width: rectangle.width - acceptedButtons: Qt.RightButton - cursorShape: parent.hoveredLink - ? Qt.PointingHandCursor - : Qt.IBeamCursor - - onClicked: mouse.button === Qt.RightButton && messageMenu.open() + lastTextSelected: message.lastTextSelected + content: $chatEntry.content + deliveryCount: deliveryLayout.model.count + onDeliveryStatusClecked: deliveryLayout.visible = !deliveryLayout.visible } } @@ -183,46 +132,13 @@ Item { leftMargin: ChatStyle.entry.message.extraContent.leftMargin } } - GridView{ + ChatDeliveries{ id: deliveryLayout anchors.top:rectangle.bottom anchors.left:parent.left anchors.right:parent.right anchors.rightMargin: 50 - //height: visible ? ChatStyle.composingText.height*container.proxyModel.composers.length : 0 - height: visible ? (ChatStyle.composingText.height-5)*deliveryLayout.model.count : 0 - cellWidth: parent.width; cellHeight: ChatStyle.composingText.height-5 - visible:false - model: ParticipantImdnStateProxyModel{ - id: imdnStatesModel - chatMessageModel: $chatEntry - } - function getText(state, displayName, stateChangeTime){ - if(state == LinphoneEnums.ChatMessageStateDelivered) - //: 'Send to %1 - %2' Little message to indicate the state of a message - //~ Context %1 is someone, %2 is a date/time. The state is that the message has been sent but not received. - return qsTr('deliveryDelivered').arg(displayName).arg(stateChangeTime) - else if(state == LinphoneEnums.ChatMessageStateDeliveredToUser) - //: 'Retrieved by %1 - %2' Little message to indicate the state of a message - //~ Context %1 is someone, %2 is a date/time. The state is that the message has been retrieved - return qsTr('deliveryDeliveredToUser').arg(displayName).arg(stateChangeTime) - else if(state == LinphoneEnums.ChatMessageStateDisplayed) - //: 'Read by %1 - %2' Little message to indicate the state of a message - //~ Context %1 is someone, %2 is a date/time. The state that the message has been read. - return qsTr('deliveryDisplayed').arg(displayName).arg(stateChangeTime) - else if(state == LinphoneEnums.ChatMessageStateNotDelivered) - //: "%1 have nothing received" Little message to indicate the state of a message - //~ Context %1 is someone. The state is that the message hasn't been delivered. - return qsTr('deliveryNotDelivered').arg(displayName) - else return '' - } - delegate:Text{ - height: ChatStyle.composingText.height-5 - width: GridView.width - text: deliveryLayout.getText(modelData.state, modelData.displayName, UtilsCpp.toDateTimeString(modelData.stateChangeTime)) - color: "#B1B1B1" - font.pointSize: Units.dp * 8 - elide: Text.ElideMiddle - } + + chatMessageModel: $chatEntry } }