linphone-desktop/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml
Julien Wadel 44b7c112b3 Multi fixes:
- Contact update propagated through timelines and more robust display name showing by using cpp.
- ContactEdit : don't use the conference URI when adding the contact to friend on secure chat room
- Conference View : Resize record, add record file notification.
- Group Chat : timed invitation (30s) where it will hide if no response. It will be shown if the participant accept later.
- Hide/Show empty chats : set defaults to avoid differences from SDK value.
- Calls actions : Dynamic width on transfer menu to show all text, set texts to uppercase.
- Timeline : Add a tooltip (right-clic) to show the last time update.
- File Messages : add search shortcut on avatar like messages, remove conflicts between right-clic menu and thumbnails increase (on hovering mouse).
- Call : hold view with a correct symbol (#10073 instead of 2 differents symbols for 2 vertical bars)
- Languages : remove '-----' artifacts in language files
2021-10-20 17:12:11 +02:00

425 lines
12 KiB
QML

import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Common 1.0
import Linphone 1.0
import LinphoneUtils 1.0
import LinphoneEnums 1.0
import Linphone.Styles 1.0
import Utils 1.0
import Units 1.0
import ColorsList 1.0
// =============================================================================
Row {
id:mainRow
// ---------------------------------------------------------------------------
// Avatar if it's an incoming message.
// ---------------------------------------------------------------------------
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
isClickable: true
onDoubleClicked: {
window.mainSearchBar.text = $chatEntry.fromSipAddress
}
}
}
}
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 ? 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.
// ---------------------------------------------------------------------
Item{
Layout.fillWidth: true
Layout.fillHeight: true
Column {
anchors.fill: parent
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 ''
}
}
}
ChatMenu{
id: chatMenu
anchors.fill: parent
//height: parent.height
//width: parent.width
deliveryCount: deliveryLayout.model.count
onDeliveryStatusClicked: deliveryLayout.visible = !deliveryLayout.visible
onRemoveEntryRequested: removeEntry()
deliveryVisible: deliveryLayout.visible
}
}
}
Icon {
id:downloadButton
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: downloadButton.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
thumbnailProvider.state = ''
} else {
rectangle.contentModel.downloadFile()
thumbnailProvider.state = ''
}
}
onExited: thumbnailProvider.state = ''
onMouseXChanged: handleMouseMove.call(this, mouse)
onMouseYChanged: handleMouseMove.call(this, mouse)
}
Row{
id:ephemeralTimerRow
anchors.right:downloadButton.visible?downloadButton.left:parent.right
anchors.bottom:parent.bottom
anchors.bottomMargin: 5
anchors.rightMargin : 5
visible:$chatEntry.isEphemeral
spacing:5
Text{
text: $chatEntry.ephemeralExpireTime > 0 ? Utils.formatElapsedTime($chatEntry.ephemeralExpireTime) : Utils.formatElapsedTime($chatEntry.ephemeralLifetime)
color: ColorsList.add("FileMessage_ephemeral_text", "ad").color
font.pointSize: Units.dp * 8
Timer{
running:parent.visible
interval: 1000
repeat:true
onTriggered: if($chatEntry.getEphemeralExpireTime() > 0 ) parent.text = Utils.formatElapsedTime($chatEntry.getEphemeralExpireTime())// Use the function
}
}
Icon{
icon:'timer'
iconSize: 15
}
}
}
ChatDeliveries{
id: deliveryLayout
anchors.top:rectangle.bottom
anchors.left:parent.left
anchors.right:parent.right
anchors.rightMargin: 50
chatMessageModel: $chatEntry
}
ActionButton {
height: ChatStyle.entry.lineHeight
anchors.left:rectangle.right
anchors.leftMargin: -10
anchors.top:rectangle.top
anchors.topMargin: 5
icon: 'chat_menu'
iconSize: ChatStyle.entry.deleteIconSize
visible: isHoverEntry()
onClicked: chatMenu.open()
}
}
// -------------------------------------------------------------------------
// 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
}
}
}
}