diff --git a/CHANGELOG.md b/CHANGELOG.md index 409be8e68..f1f472690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Primary color for links in chat. - Replace double click on avatar by a simple click for copying address into the SmartSearchBar. +- Bubble chat layout. ### Added - VFS Encryption @@ -26,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Emojis picker. - Text edit in chat can now understand rich texts. - Create thumbnails into memory instead of disk. +- Display video thumbnails. +- Crop thumbnail and pictures if distored. + ### Removed - Picture zoom on mouse over. diff --git a/linphone-app/CMakeLists.txt b/linphone-app/CMakeLists.txt index 4c6a71f01..63b6607db 100644 --- a/linphone-app/CMakeLists.txt +++ b/linphone-app/CMakeLists.txt @@ -132,7 +132,7 @@ if( WIN32) endif() set(CMAKE_INCLUDE_CURRENT_DIR ON)#useful for config.h -set(QT5_PACKAGES Core Gui Quick Widgets QuickControls2 Svg LinguistTools Concurrent Network Test Qml) +set(QT5_PACKAGES Core Gui Quick Widgets QuickControls2 Svg LinguistTools Concurrent Network Test Qml Multimedia) if(ENABLE_APP_OAUTH2) list(APPEND QT5_PACKAGES NetworkAuth) diff --git a/linphone-app/assets/images/thumbnail_video_custom.svg b/linphone-app/assets/images/thumbnail_video_custom.svg new file mode 100644 index 000000000..0bb6847e9 --- /dev/null +++ b/linphone-app/assets/images/thumbnail_video_custom.svg @@ -0,0 +1,46 @@ + + + + + + + + + diff --git a/linphone-app/resources.qrc b/linphone-app/resources.qrc index 2af508a45..a8b75859d 100644 --- a/linphone-app/resources.qrc +++ b/linphone-app/resources.qrc @@ -164,6 +164,7 @@ assets/images/stop_fullscreen_custom.svg assets/images/timer_custom.svg assets/images/tel_keypad_voicemail_custom.svg + assets/images/thumbnail_video_custom.svg assets/images/tooltip_arrow_bottom_custom.svg assets/images/tooltip_arrow_left_custom.svg assets/images/tooltip_arrow_right_custom.svg @@ -412,6 +413,7 @@ ui/modules/Linphone/Styles/Dialog/OnlineInstallerDialogStyle.qml ui/modules/Linphone/Styles/Dialog/SipAddressDialogStyle.qml ui/modules/Linphone/Styles/Dialog/ZrtpTokenAuthenticationDialogStyle.qml + ui/modules/Linphone/Styles/File/FileViewStyle.qml ui/modules/Linphone/Styles/History/HistoryStyle.qml ui/modules/Linphone/Styles/Menus/SipAddressesMenuStyle.qml ui/modules/Linphone/Styles/Menus/IncallMenuStyle.qml diff --git a/linphone-app/src/app/providers/ExternalImageProvider.cpp b/linphone-app/src/app/providers/ExternalImageProvider.cpp index 9aeb06f18..7e0bab3f9 100644 --- a/linphone-app/src/app/providers/ExternalImageProvider.cpp +++ b/linphone-app/src/app/providers/ExternalImageProvider.cpp @@ -35,8 +35,17 @@ ExternalImageProvider::ExternalImageProvider () : QQuickImageProvider( ) { } -QImage ExternalImageProvider::requestImage (const QString &id, QSize *size, const QSize &) { - QImage image(Utils::getImage(QUrl::fromPercentEncoding(id.toUtf8()))); - *size = image.size(); - return image; +QImage ExternalImageProvider::requestImage (const QString &id, QSize *size, const QSize &requestedSize) { + QImage image(Utils::getImage(QUrl::fromPercentEncoding(id.toUtf8()))); + double requestedFactor = 1.0; + double factor = image.width() / (double)image.height(); + if(requestedSize.isValid()) + requestedFactor = requestedSize.width() / (double)requestedSize.height(); + if(factor <0.2){// too height + image = image.copy(0,0, image.width(), image.width() / requestedFactor); + }else if( factor > 5){// too large + image = image.copy(0,0, image.height() * requestedFactor, image.height()); + } + *size = image.size(); + return image; } diff --git a/linphone-app/src/components/other/images/ImageModel.cpp b/linphone-app/src/components/other/images/ImageModel.cpp index a9601eec4..aa4340814 100644 --- a/linphone-app/src/components/other/images/ImageModel.cpp +++ b/linphone-app/src/components/other/images/ImageModel.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "app/App.hpp" #include "utils/Utils.hpp" @@ -30,6 +31,75 @@ #include "components/core/CoreManager.hpp" #include "utils/QExifImageHeader.hpp" +#include + +VideoFrameGrabber::VideoFrameGrabber( QObject *parent) + : QAbstractVideoSurface(parent){ +} + +QList VideoFrameGrabber::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const { + Q_UNUSED(handleType); + return QList() + << QVideoFrame::Format_ARGB32 + << QVideoFrame::Format_ARGB32_Premultiplied + << QVideoFrame::Format_RGB32 + << QVideoFrame::Format_RGB24 + << QVideoFrame::Format_RGB565 + << QVideoFrame::Format_RGB555 + << QVideoFrame::Format_ARGB8565_Premultiplied + << QVideoFrame::Format_BGRA32 + << QVideoFrame::Format_BGRA32_Premultiplied + << QVideoFrame::Format_BGR32 + << QVideoFrame::Format_BGR24 + << QVideoFrame::Format_BGR565 + << QVideoFrame::Format_BGR555 + << QVideoFrame::Format_BGRA5658_Premultiplied + << QVideoFrame::Format_AYUV444 + << QVideoFrame::Format_AYUV444_Premultiplied + << QVideoFrame::Format_YUV444 + << QVideoFrame::Format_YUV420P + << QVideoFrame::Format_YV12 + << QVideoFrame::Format_UYVY + << QVideoFrame::Format_YUYV + << QVideoFrame::Format_NV12 + << QVideoFrame::Format_NV21 + << QVideoFrame::Format_IMC1 + << QVideoFrame::Format_IMC2 + << QVideoFrame::Format_IMC3 + << QVideoFrame::Format_IMC4 + << QVideoFrame::Format_Y8 + << QVideoFrame::Format_Y16 + << QVideoFrame::Format_Jpeg + << QVideoFrame::Format_CameraRaw + << QVideoFrame::Format_AdobeDng; +} + +bool VideoFrameGrabber::isFormatSupported(const QVideoSurfaceFormat &format) const { + const QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat()); + const QSize size = format.frameSize(); + + return imageFormat != QImage::Format_Invalid + && !size.isEmpty() + && format.handleType() == QAbstractVideoBuffer::NoHandle; +} + +bool VideoFrameGrabber::start(const QVideoSurfaceFormat &format){ + return QAbstractVideoSurface::start(format); +} + +void VideoFrameGrabber::stop() { + QAbstractVideoSurface::stop(); +} + +bool VideoFrameGrabber::present(const QVideoFrame &frame){ + if (frame.isValid()) { + emit frameAvailable(frame.image()); + return true; + }else + return false; +} + + // ============================================================================= ImageModel::ImageModel (const QString& id, const QString& path, const QString& description, QObject * parent) : QObject(parent) { @@ -88,6 +158,25 @@ QImage ImageModel::createThumbnail(const QString& path){ QByteArray format = reader.format(); if(!format.isEmpty()) originalImage = QImage(path, format); + else if(Utils::isVideo(path)){ + QMediaPlayer player; + player.setMedia(QUrl::fromLocalFile(path)); + player.setPosition(player.duration() / 2); + VideoFrameGrabber grabber; + player.setVideoOutput(&grabber); + QObject * context = new QObject(); + QObject::connect(&grabber, &VideoFrameGrabber::frameAvailable, context, [&context,&originalImage, &player](QImage frame) mutable{ + originalImage = frame.copy(); + player.stop(); + context->deleteLater();// This will destroy context and initializer + context = nullptr; + }, Qt::DirectConnection); + player.play(); + do{ + qApp->processEvents(); + }while(player.state() != QMediaPlayer::State::StoppedState); + if(context) context->deleteLater(); + } } if (!originalImage.isNull()){ int rotation = 0; @@ -101,26 +190,27 @@ QImage ImageModel::createThumbnail(const QString& path){ painter.drawImage(0, 0, originalImage); //-------------------- double factor = image.width() / (double)image.height(); - if(factor < 0.2 || factor > 5){ - qInfo() << QStringLiteral("Cannot create thumbnails because size factor (%1) is too low/much of: `%2`.").arg(factor).arg(path); - }else { - thumbnail = image.scaled( + Qt::AspectRatioMode aspectRatio = Qt::KeepAspectRatio; + if(factor < 0.2 || factor > 5) + aspectRatio = Qt::KeepAspectRatioByExpanding; + thumbnail = image.scaled( Constants::ThumbnailImageFileWidth, Constants::ThumbnailImageFileHeight, - Qt::KeepAspectRatio, Qt::SmoothTransformation + aspectRatio , Qt::SmoothTransformation ); + if(aspectRatio == Qt::KeepAspectRatioByExpanding) + thumbnail = thumbnail.copy(0,0,Constants::ThumbnailImageFileWidth, Constants::ThumbnailImageFileHeight); - if (rotation != 0) { - QTransform transform; - if (rotation == 3 || rotation == 4) - transform.rotate(180); - else if (rotation == 5 || rotation == 6) - transform.rotate(90); - else if (rotation == 7 || rotation == 8) - transform.rotate(-90); - thumbnail = thumbnail.transformed(transform); - if (rotation == 2 || rotation == 4 || rotation == 5 || rotation == 7) - thumbnail = thumbnail.mirrored(true, false); - } + if (rotation != 0) { + QTransform transform; + if (rotation == 3 || rotation == 4) + transform.rotate(180); + else if (rotation == 5 || rotation == 6) + transform.rotate(90); + else if (rotation == 7 || rotation == 8) + transform.rotate(-90); + thumbnail = thumbnail.transformed(transform); + if (rotation == 2 || rotation == 4 || rotation == 5 || rotation == 7) + thumbnail = thumbnail.mirrored(true, false); } } } diff --git a/linphone-app/src/components/other/images/ImageModel.hpp b/linphone-app/src/components/other/images/ImageModel.hpp index a16c6e00a..114f74cd3 100644 --- a/linphone-app/src/components/other/images/ImageModel.hpp +++ b/linphone-app/src/components/other/images/ImageModel.hpp @@ -28,6 +28,25 @@ #include #include "utils/LinphoneEnums.hpp" +#include + + +class VideoFrameGrabber : public QAbstractVideoSurface { + Q_OBJECT + public: + VideoFrameGrabber(QObject *parent = 0); + + QList supportedPixelFormats( + QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const override; + bool isFormatSupported(const QVideoSurfaceFormat &format) const override; + + bool start(const QVideoSurfaceFormat &format) override; + void stop() override; + bool present(const QVideoFrame &frame) override; + + signals: + void frameAvailable(QImage frame); +}; class ImageModel : public QObject { Q_OBJECT diff --git a/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp b/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp index ea2e9b6c8..82e1af73b 100644 --- a/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp +++ b/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp @@ -50,6 +50,9 @@ ParticipantDeviceListModel::ParticipantDeviceListModel (CallModel * callModel, Q } } +ParticipantDeviceListModel::~ParticipantDeviceListModel(){ +} + void ParticipantDeviceListModel::initConferenceModel(){ if(!mInitialized && mCallModel){ auto conferenceModel = mCallModel->getConferenceSharedModel(); diff --git a/linphone-app/src/components/participant/ParticipantDeviceListModel.hpp b/linphone-app/src/components/participant/ParticipantDeviceListModel.hpp index ed5edc3c1..9f0790122 100644 --- a/linphone-app/src/components/participant/ParticipantDeviceListModel.hpp +++ b/linphone-app/src/components/participant/ParticipantDeviceListModel.hpp @@ -38,7 +38,7 @@ class ParticipantDeviceListModel : public ProxyListModel { public: ParticipantDeviceListModel (std::shared_ptr participant, QObject *parent = nullptr); ParticipantDeviceListModel (CallModel * callModel, QObject *parent = nullptr); - + ~ParticipantDeviceListModel(); void initConferenceModel(); void updateDevices(std::shared_ptr participant); void updateDevices(const std::list>& devices, const bool& isMe); diff --git a/linphone-app/src/utils/Utils.cpp b/linphone-app/src/utils/Utils.cpp index 8cb7b77c5..3c2a9629b 100644 --- a/linphone-app/src/utils/Utils.cpp +++ b/linphone-app/src/utils/Utils.cpp @@ -650,6 +650,10 @@ bool Utils::isSupportedForDisplay(const QString& path){ return !QMimeDatabase().mimeTypeForFile(path).name().contains("application");// "for pdf : "application/pdf". Note : Make an exception when supported. } +bool Utils::canHaveThumbnail(const QString& path){ + return isImage(path) || isAnimatedImage(path) || isPdf(path) || isVideo(path); +} + bool Utils::isPhoneNumber(const QString& txt){ auto core = CoreManager::getInstance()->getCore(); if(!core) diff --git a/linphone-app/src/utils/Utils.hpp b/linphone-app/src/utils/Utils.hpp index 1e6b846bd..43710e8f4 100644 --- a/linphone-app/src/utils/Utils.hpp +++ b/linphone-app/src/utils/Utils.hpp @@ -71,6 +71,7 @@ public: Q_INVOKABLE static bool isVideo(const QString& path); Q_INVOKABLE static bool isPdf(const QString& path); Q_INVOKABLE static bool isSupportedForDisplay(const QString& path); + Q_INVOKABLE static bool canHaveThumbnail(const QString& path); Q_INVOKABLE static bool isPhoneNumber(const QString& txt); Q_INVOKABLE static bool isUsername(const QString& txt); // Check with Regex Q_INVOKABLE QSize getImageSize(const QString& url); diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatConferenceInvitationMessage.qml b/linphone-app/ui/modules/Linphone/Chat/ChatConferenceInvitationMessage.qml index bf7c883ee..47229ba7e 100644 --- a/linphone-app/ui/modules/Linphone/Chat/ChatConferenceInvitationMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/ChatConferenceInvitationMessage.qml @@ -47,10 +47,6 @@ Loader{ property int fitWidth: layout.fitWidth + ChatCalendarMessageStyle.leftMargin+ChatCalendarMessageStyle.rightMargin anchors.fill: parent - anchors.leftMargin: ChatCalendarMessageStyle.widthMargin - anchors.rightMargin: ChatCalendarMessageStyle.widthMargin - anchors.topMargin: ChatCalendarMessageStyle.topMargin - anchors.bottomMargin: ChatCalendarMessageStyle.bottomMargin clip: false diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatContent.qml b/linphone-app/ui/modules/Linphone/Chat/ChatContent.qml index 7c0b12072..1ca050026 100644 --- a/linphone-app/ui/modules/Linphone/Chat/ChatContent.qml +++ b/linphone-app/ui/modules/Linphone/Chat/ChatContent.qml @@ -23,10 +23,10 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically) id: mainItem property ChatMessageModel chatMessageModel: null property int availableWidth //const - property int fileWidth: ChatStyle.entry.message.file.height * 4 / 3 + 2*ChatStyle.entry.message.file.margins + property int fileWidth: FileViewStyle.height * 4 / 3 + 2*ChatStyle.entry.message.file.margins // Readonly - property int bestWidth: Math.min(availableWidth, Math.max(filesBestWidth, conferencesBestWidth, textsBestWidth, voicesBestWidth)) + property int bestWidth: Math.min(availableWidth, Math.max(filesCount*filesBestWidth, conferencesBestWidth, textsBestWidth, voicesBestWidth)) property int filesBestWidth: 0 property int filesCount: 0 property int conferencesCount: 0 @@ -49,11 +49,12 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically) property int fileBackgroundRadius: ChatStyle.entry.message.file.extension.radius active: chatMessageModel - + sourceComponent: Component{ Column{ id: mainComponent spacing: 0 + padding: 10 function updateFilesBestWidth(){ var newBestWidth = 0 var count = 0 @@ -63,13 +64,10 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically) var a = item.fitWidth if(a) { ++count - newBestWidth = Math.max(newBestWidth,a) + newBestWidth = Math.max(newBestWidth,a+2*ChatStyle.entry.message.file.margins) } } } - if(count > 1){ - newBestWidth = Math.max(newBestWidth, mainItem.fileWidth*count) - } mainItem.filesCount = count mainItem.filesBestWidth = newBestWidth } @@ -87,7 +85,7 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically) } ListView { id: messagesVoicesList - width: parent.width + width: parent.width-2*mainComponent.padding visible: count > 0 spacing: 0 clip: false @@ -115,7 +113,7 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically) // CONFERENCE ListView { id: messagesConferencesList - width: parent.width + width: parent.width-2*mainComponent.padding visible: count > 0 spacing: 0 clip: false @@ -151,14 +149,14 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically) id: messageFilesList property alias count: repeater.count visible: count > 0 - clip: false + clip: false + width: parent.width-2*mainComponent.padding - property int availableSection: mainItem.availableWidth / mainItem.fileWidth - property int bestFitSection: mainItem.bestWidth / mainItem.fileWidth + property int availableSection: mainItem.availableWidth / mainItem.filesBestWidth + property int bestFitSection: mainItem.bestWidth / mainItem.filesBestWidth columns: Math.max(1, Math.min(availableSection , bestFitSection)) columnSpacing: 0 - rowSpacing: 0 - width: parent.width + rowSpacing: ChatStyle.entry.message.file.spacing Repeater{ id: repeater model: ContentProxyModel{ @@ -166,6 +164,12 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically) chatMessageModel: mainItem.chatMessageModel } ChatFileMessage{ + Layout.fillHeight: true + Layout.fillWidth: true + Layout.preferredHeight: fitHeight + Layout.preferredWidth: fitWidth + Layout.maximumWidth: fitWidth + Layout.maximumHeight: fitHeight contentModel: $modelData onIsHoveringChanged: mainItem.isFileHoveringChanged(isHovering) borderWidth: mainItem.fileBorderWidth @@ -178,7 +182,7 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically) // TEXTS ListView { id: messagesTextsList - width: parent.width + width: parent.width-2*mainComponent.padding visible: count > 0 spacing: 0 clip: false @@ -192,11 +196,14 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically) function updateBestWidth(){ var newWidth = mainComponent.updateListBestWidth(messagesTextsList) mainItem.textsCount = newWidth[0] - mainItem.textsBestWidth = newWidth[1] + // Padding is takken account because it is used for the whole bubble. + // We add 1 pixel to avoid implicit new line computation (Guess : float computation from Qt) + mainItem.textsBestWidth = newWidth[1] + 2*mainComponent.padding + 1 } Component.onCompleted: messagesTextsList.updateBestWidth() delegate: ChatTextMessage { + width: parent.width contentModel: $modelData onLastTextSelectedChanged: mainItem.lastTextSelectedChanged(lastTextSelected) color: mainItem.useTextColor diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatFileMessage.qml b/linphone-app/ui/modules/Linphone/Chat/ChatFileMessage.qml index 5bacbcede..45dcefaf4 100644 --- a/linphone-app/ui/modules/Linphone/Chat/ChatFileMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/ChatFileMessage.qml @@ -13,270 +13,35 @@ import UtilsCpp 1.0 // ============================================================================= // TODO : into Loader -Row { +Item { id:mainRow property ChatMessageModel chatMessageModel: contentModel && contentModel.chatMessageModel property ContentModel contentModel - property bool isOutgoing : chatMessageModel && ( chatMessageModel.isOutgoing || chatMessageModel.state == LinphoneEnums.ChatMessageStateIdle); - property int fitHeight: mainRow.isAnimatedImage ? ChatStyle.entry.message.file.heightbetter : ChatStyle.entry.message.file.height - property int fitWidth: fitHeight * 4 / 3 + 2*ChatStyle.entry.message.file.margins - property int borderWidth : 0 - property color backgroundColor: ChatStyle.entry.message.file.extension.background.colorModel.color - property int backgroundRadius: ChatStyle.entry.message.file.extension.radius - /* - property int fitWidth: visible - ? Math.max( Math.max((thumbnailProvider.sourceComponent == extension - ? thumbnailProvider.item.fitWidth - : 0) - , thumbnailProvider.width + 3*ChatStyle.entry.message.file.margins) - , Math.max(ChatStyle.entry.message.file.width, ChatStyle.entry.message.outgoing.areaSize)) - : 0 - property int fitHeight: visible ? rectangle.height : 0 - */ - property bool isAnimatedImage : mainRow.contentModel && mainRow.contentModel.wasDownloaded && UtilsCpp.isAnimatedImage(mainRow.contentModel.filePath) - property bool haveThumbnail: mainRow.contentModel && mainRow.contentModel.thumbnail - property bool isHovering: thumbnailProvider.state == 'hovered' + property int fitHeight: fileView.fitHeight + property int fitWidth: fileView.fitWidth + property alias borderWidth: fileView.borderWidth + property alias backgroundColor: fileView.backgroundColor + property alias backgroundRadius: fileView.backgroundRadius + property alias isHovering: fileView.isHovering - signal copyAllDone() - signal copySelectionDone() - signal forwardClicked() - height: fitHeight - width: fitWidth - visible: true // --------------------------------------------------------------------------- // File message. // --------------------------------------------------------------------------- - - Item{ - width: mainRow.width - height:rectangle.height + Rectangle { + id: rectangle + color: 'transparent' + anchors.fill: parent + radius: ChatStyle.entry.message.radius - Rectangle { - id: rectangle - - readonly property bool isError: chatMessageModel && Utils.includes([ - LinphoneEnums.ChatMessageStateFileTransferError, - LinphoneEnums.ChatMessageStateNotDelivered, - ], chatMessageModel.state) - readonly property bool isUploaded: chatMessageModel && chatMessageModel.state == LinphoneEnums.ChatMessageStateDelivered - readonly property bool isDelivered: chatMessageModel && chatMessageModel.state == LinphoneEnums.ChatMessageStateDeliveredToUser - readonly property bool isRead: chatMessageModel && chatMessageModel.state == LinphoneEnums.ChatMessageStateDisplayed - readonly property bool isTransferring: chatMessageModel && (chatMessageModel.state == LinphoneEnums.ChatMessageStateFileTransferInProgress || chatMessageModel.state == LinphoneEnums.ChatMessageStateInProgress ) - - property string thumbnail : mainRow.contentModel ? mainRow.contentModel.thumbnail : '' - color: 'transparent' - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - height: 2*ChatStyle.entry.message.file.margins + (mainRow.isAnimatedImage - ? ChatStyle.entry.message.file.heightbetter - : thumbnailProvider.sourceComponent == extension - ? ChatStyle.entry.message.file.height - : ChatStyle.entry.message.file.height - ) - radius: ChatStyle.entry.message.radius - - // --------------------------------------------------------------------- - // Thumbnail or extension. - // --------------------------------------------------------------------- - - Component { - id: thumbnailImage - - Image { - id: thumbnailImageSource - property real scaleAnimatorTo : ChatStyle.entry.message.file.animation.thumbnailTo - anchors.centerIn: parent - mipmap: SettingsModel.mipmapEnabled - source: mainRow.contentModel.thumbnail - autoTransform: true - fillMode: Image.PreserveAspectFit - height: ChatStyle.entry.message.file.height - width: height*4/3 - Component.onCompleted: mainRow.fitHeight = height - - Loader{ - anchors.fill: parent - sourceComponent: Image{// Better quality on zoom - mipmap: SettingsModel.mipmapEnabled - source:'image://external/'+mainRow.contentModel.filePath - autoTransform: true - fillMode: Image.PreserveAspectFit - visible: status == Image.Ready - } - asynchronous: true - active: thumbnailProvider.state == 'hovered' - } - } - } - Component { - id: animatedImage - - AnimatedImage { - id: animatedImageSource - property real scaleAnimatorTo : ChatStyle.entry.message.file.animation.to - mipmap: SettingsModel.mipmapEnabled - source: 'file:/'+mainRow.contentModel.filePath - autoTransform: true - fillMode: Image.PreserveAspectFit - height: ChatStyle.entry.message.file.heightbetter - width: height*4/3 - - Component.onCompleted: mainRow.fitHeight = height - } - } - - Component { - id: extension - - Rectangle { - property int fitWidth: Math.max(downloadText.implicitWidth, Math.max(fileName.visible ? fileName.implicitWidth : 0, fileIcon.iconSize)) + 20 - //property int fitHeight: fileIcon.iconSize + (fileName.visible ? fileName.implicitHeight + ChatStyle.entry.message.file.spacing : 0 ) -// + (downloadText.visible? downloadText.implicitHeight + ChatStyle.entry.message.file.spacing : 0) + 2*ChatStyle.entry.message.file.margins - property real scaleAnimatorTo : ChatStyle.entry.message.file.animation.to - - anchors.centerIn: parent - height: ChatStyle.entry.message.file.height - width: height*4/3 - color: mainRow.backgroundColor - radius: mainRow.backgroundRadius - border.width: mainRow.borderWidth - border.color: ChatStyle.entry.message.file.extension.background.borderColorModel.color - - ColumnLayout{ - anchors.fill: parent - anchors.topMargin: ChatStyle.entry.message.file.margins - anchors.bottomMargin: ChatStyle.entry.message.file.margins - spacing: ChatStyle.entry.message.file.spacing - Icon{ - id: fileIcon - Layout.alignment: Qt.AlignCenter - icon: extensionText.text != '' ? ChatStyle.entry.message.file.extension.icon : ChatStyle.entry.message.file.extension.unknownIcon - iconSize: ChatStyle.entry.message.file.extension.iconSize - Layout.fillHeight: true - Layout.fillWidth: true - Layout.preferredHeight: iconSize - Layout.preferredWidth: iconSize - Text { - id: extensionText - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottomMargin: ChatStyle.entry.message.file.spacing - width: parent.width - 2*ChatStyle.entry.message.file.spacing - color: ChatStyle.entry.message.file.extension.text.colorModel.color - font.bold: true - font.pointSize: ChatStyle.entry.message.file.extension.text.pointSize - clip: true - text: (mainRow.contentModel?Utils.getExtension(mainRow.contentModel.name).toUpperCase():'') - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - RoundProgressBar { - id: progressBar - anchors.centerIn: parent - property int fileSize: mainRow.contentModel ? mainRow.contentModel.fileSize : 0 - to: 100 - value: mainRow.contentModel ? (fileSize>0 ? Math.floor(100 * mainRow.contentModel.fileOffset / fileSize) : 0) : to - visible: rectangle.isTransferring && value != 0 - /* Change format? Current is % - text: if(mainRow.contentModel){ - var fileSize = Utils.formatSize(mainRow.contentModel.fileSize) - return progressBar.visible - ? Utils.formatSize(mainRow.contentModel.fileOffset) + '/' + fileSize - : fileSize - }else - return '' - */ - } - } - Text { - id: fileName - Layout.fillWidth: true - Layout.fillHeight: true - visible: mainRow.contentModel && !mainRow.isAnimatedImage && !mainRow.haveThumbnail - - color: ChatStyle.entry.message.file.extension.text.colorModel.color - font.pointSize: ChatStyle.entry.message.file.name.pointSize - wrapMode: Text.WrapAnywhere - horizontalAlignment: Text.AlignHCenter - maximumLineCount: 2 - - text: (mainRow.contentModel ? mainRow.contentModel.name : '') - } - Text{ - id: downloadText - Layout.fillWidth: true - Layout.fillHeight: true - Layout.preferredHeight: visible ? contentHeight : 0 - //: 'Cancel' : Message link to cancel a transfer (upload/download) - text: mainRow.contentModel ? rectangle.isTransferring ? qsTr('fileTransferCancel') - //: 'Download' : Message link to download a file - : qsTr('fileTransferDownload') +' ('+Utils.formatSize(mainRow.contentModel.fileSize)+')' - : '' - font.underline: true - font.pointSize: ChatStyle.entry.message.file.download.pointSize - color:ChatStyle.entry.message.file.extension.text.colorModel.color - visible: (mainRow.contentModel? (!mainItem.isOutgoing && !mainRow.contentModel.wasDownloaded) || rectangle.isTransferring : false) - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - - } - } - Loader { - id: thumbnailProvider - anchors.centerIn: parent - sourceComponent: (mainRow.contentModel ? - (mainRow.isAnimatedImage ? animatedImage - : (mainRow.haveThumbnail ? thumbnailImage : extension ) - ) : undefined) - - states: State { - name: 'hovered' - } - - } - } - - - MouseArea { - function handleMouseMove (mouse) { - thumbnailProvider.state = Utils.pointIsInItem(this, thumbnailProvider, mouse) - ? 'hovered' - : '' - } - + FileView{ + id: fileView anchors.fill: parent - visible: true - - onClicked: { - if(rectangle.isTransferring) - mainRow.contentModel.cancelDownloadFile() - else if( !mainRow.contentModel.wasDownloaded) { - thumbnailProvider.state = '' - mainRow.contentModel.downloadFile() - }else if (Utils.pointIsInItem(this, thumbnailProvider, mouse)) { - if(SettingsModel.isVfsEncrypted){ - window.attachVirtualWindow(Utils.buildCommonDialogUri('FileViewDialog'), { - contentModel: mainRow.contentModel, - }, function (status) { - }) - }else - mainRow.contentModel.openFile() - } else if (mainRow.contentModel ) { - thumbnailProvider.state = '' - mainRow.contentModel.openFile(true)// Show directory - } else { - thumbnailProvider.state = '' - mainRow.contentModel.downloadFile() - - } - } - onExited: thumbnailProvider.state = '' - onMouseXChanged: handleMouseMove.call(this, mouse) - onMouseYChanged: handleMouseMove.call(this, mouse) + contentModel: mainRow.contentModel + thumbnail: mainRow.contentModel.thumbnail + name: mainRow.contentModel && mainRow.contentModel.name + filePath: mainRow.contentModel && mainRow.contentModel.filePath + isTransferring: mainRow.chatMessageModel && (mainRow.chatMessageModel.state == LinphoneEnums.ChatMessageStateFileTransferInProgress || mainRow.chatMessageModel.state == LinphoneEnums.ChatMessageStateInProgress ) } } -} +} \ No newline at end of file diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatFilePreview.qml b/linphone-app/ui/modules/Linphone/Chat/ChatFilePreview.qml index b327f15c6..7728980f3 100644 --- a/linphone-app/ui/modules/Linphone/Chat/ChatFilePreview.qml +++ b/linphone-app/ui/modules/Linphone/Chat/ChatFilePreview.qml @@ -49,12 +49,10 @@ Item{ width: height * ChatFilePreviewStyle.filePreview.format anchors.verticalCenter: parent ? parent.verticalCenter : ScrollableListView.verticalCenter anchors.verticalCenterOffset: 7 + contentModel: $modelData thumbnail: $modelData.thumbnail name: $modelData.name animationScale: 1.1 - onClickOnFile: { - $modelData.openFile() - } ActionButton{ anchors.bottom: parent.top anchors.bottomMargin: -height/2 diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatTextMessage.qml b/linphone-app/ui/modules/Linphone/Chat/ChatTextMessage.qml index 3c179ccc8..008f0b397 100644 --- a/linphone-app/ui/modules/Linphone/Chat/ChatTextMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/ChatTextMessage.qml @@ -23,8 +23,8 @@ TextEdit { property ContentModel contentModel property string lastTextSelected : '' property font customFont : SettingsModel.textMessageFont - property int fitHeight: contentHeight + padding + 8 - property int fitWidth: implicitWidth + 2 // add 2 because there is a bug on border that lead to not fit text exactly + property int fitHeight: contentHeight + property int fitWidth: implicitWidth signal rightClicked() @@ -33,9 +33,8 @@ TextEdit { height: fitHeight width: parent && parent.width || 1 - visible: contentModel// && contentModel.isText() + visible: contentModel clip: false - padding: ChatStyle.entry.message.padding textMargin: 0 readOnly: true selectByMouse: true @@ -70,7 +69,6 @@ TextEdit { } deselect() } - MouseArea { id: mouseArea property bool keepLastSelection: false diff --git a/linphone-app/ui/modules/Linphone/File/FileView.qml b/linphone-app/ui/modules/Linphone/File/FileView.qml index 8e45c0bc7..fbc1e3aa7 100644 --- a/linphone-app/ui/modules/Linphone/File/FileView.qml +++ b/linphone-app/ui/modules/Linphone/File/FileView.qml @@ -7,6 +7,7 @@ import Linphone 1.0 import LinphoneEnums 1.0 import Linphone.Styles 1.0 import Utils 1.0 +import UtilsCpp 1.0 import Units 1.0 import ColorsList 1.0 @@ -15,118 +16,249 @@ import ColorsList 1.0 Item { id: mainItem - property string thumbnail - property string name + property ContentModel contentModel + property string thumbnail: contentModel && contentModel.thumbnail + property string name: contentModel && contentModel.name + property string filePath: contentModel && contentModel.filePath + property int fileHeight: FileViewStyle.height property bool active: true - property real animationScale : ChatStyle.entry.message.file.animation.to + property real animationScale : FileViewStyle.animation.to property alias imageScale: thumbnailProvider.scale + property int fitHeight: mainItem.isAnimatedImage ? FileViewStyle.heightbetter : FileViewStyle.height + property int fitWidth: fitHeight*4/3 + property bool isAnimatedImage : contentModel && contentModel.wasDownloaded && UtilsCpp.isAnimatedImage(filePath) + property bool haveThumbnail: contentModel && UtilsCpp.canHaveThumbnail(filePath) + property int borderWidth : 0 + property color backgroundColor: FileViewStyle.extension.background.colorModel.color + property int backgroundRadius: FileViewStyle.extension.radius + + property bool isTransferring + property bool isHovering: thumbnailProvider.state == 'hovered' signal clickOnFile() - // --------------------------------------------------------------------- - // Thumbnail or extension. - // --------------------------------------------------------------------- + MouseArea { + function handleMouseMove (mouse) { + thumbnailProvider.state = Utils.pointIsInItem(this, thumbnailProvider, mouse) + ? 'hovered' + : '' + } + + anchors.fill: parent + visible: true + + onClicked: { + if(mainItem.isTransferring) + mainItem.contentModel.cancelDownloadFile() + else if( !mainItem.contentModel.wasDownloaded) { + thumbnailProvider.state = '' + mainItem.contentModel.downloadFile() + }else if (Utils.pointIsInItem(this, thumbnailProvider, mouse)) { + if(SettingsModel.isVfsEncrypted){ + window.attachVirtualWindow(Utils.buildCommonDialogUri('FileViewDialog'), { + contentModel: mainItem.contentModel, + }, function (status) { + }) + }else + mainItem.contentModel.openFile() + } else if (mainItem.contentModel ) { + thumbnailProvider.state = '' + mainItem.contentModel.openFile(true)// Show directory + } else { + thumbnailProvider.state = '' + mainItem.contentModel.downloadFile() + + } + mainItem.clickOnFile() + } + onExited: thumbnailProvider.state = '' + onMouseXChanged: handleMouseMove.call(this, mouse) + onMouseYChanged: handleMouseMove.call(this, mouse) + } + + + // --------------------------------------------------------------------- + // Thumbnail + // --------------------------------------------------------------------- Component { id: thumbnailImage Image { id: thumbnailImageSource + property real scaleAnimatorTo : FileViewStyle.animation.thumbnailTo + property bool isVideo: UtilsCpp.isVideo(mainItem.filePath) mipmap: SettingsModel.mipmapEnabled source: mainItem.thumbnail + autoTransform: true fillMode: Image.PreserveAspectFit + anchors.fill: parent + + Loader{ + anchors.fill: parent + sourceComponent: Image{// Better quality on zoom + mipmap: SettingsModel.mipmapEnabled + source: !thumbnailImageSource.isVideo ? 'image://external/'+mainItem.filePath : '' + autoTransform: true + fillMode: Image.PreserveAspectFit + visible: status == Image.Ready + } + asynchronous: true + active: !thumbnailImageSource.isVideo && thumbnailProvider.state == 'hovered' + } + ActionButton{ + id: thumbnailVideoButton + anchors.centerIn: parent + visible: thumbnailImageSource.isVideo + isCustom: true + backgroundRadius: width + colorSet: FileViewStyle.thumbnailVideoIcon + onClicked:{ + window.attachVirtualWindow(Utils.buildCommonDialogUri('FileViewDialog'), { + contentModel: mainItem.contentModel, + }, function (status) { + }) + } + } } } - + Component { + id: animatedImage + + AnimatedImage { + id: animatedImageSource + property real scaleAnimatorTo : FileViewStyle.animation.to + mipmap: SettingsModel.mipmapEnabled + source: 'file:/'+mainItem.filePath + autoTransform: true + fillMode: Image.PreserveAspectFit + anchors.fill: parent + } + } + // --------------------------------------------------------------------- + // Extension + // --------------------------------------------------------------------- Component { id: extension Rectangle { - color: ChatStyle.entry.message.file.extension.background.colorModel.color - - Text { + property real scaleAnimatorTo : FileViewStyle.animation.to + anchors.fill: parent + color: mainItem.backgroundColor + radius: mainItem.backgroundRadius + border.width: mainItem.borderWidth + border.color: FileViewStyle.extension.background.borderColorModel.color + + ColumnLayout{ anchors.fill: parent - - color: ChatStyle.entry.message.file.extension.text.colorModel.color - font.bold: true - elide: Text.ElideRight - text: Utils.getExtension(mainItem.name).toUpperCase() - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + anchors.topMargin: FileViewStyle.margins + anchors.bottomMargin: FileViewStyle.margins + spacing: FileViewStyle.spacing + Icon{ + id: fileIcon + Layout.alignment: Qt.AlignCenter + icon: extensionText.text != '' ? FileViewStyle.extension.icon : FileViewStyle.extension.unknownIcon + iconSize: FileViewStyle.extension.iconSize + Layout.fillHeight: true + Layout.fillWidth: true + Layout.preferredHeight: iconSize + Layout.preferredWidth: iconSize + Text { + id: extensionText + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: FileViewStyle.spacing + width: FileViewStyle.extension.internalSize + onWidthChanged: extensionMetrics.font.pointSize = FileViewStyle.extension.text.pointSize // reset metrics + color: FileViewStyle.extension.text.colorModel.color + font.bold: true + font.pointSize: extensionMetrics.font.pointSize + clip: true + text: (mainItem.contentModel?Utils.getExtension(mainItem.name).toUpperCase():'') + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + TextMetrics{ + id: extensionMetrics + text: extensionText.text + font.pointSize: FileViewStyle.extension.text.pointSize + onWidthChanged: if(width > extensionText.width) --font.pointSize + Component.onCompleted: if(width > extensionText.width) --font.pointSize + } + } + RoundProgressBar { + id: progressBar + anchors.centerIn: parent + property int fileSize: mainItem.contentModel ? mainItem.contentModel.fileSize : 0 + to: 100 + value: mainItem.contentModel ? (fileSize>0 ? Math.floor(100 * mainItem.contentModel.fileOffset / fileSize) : 0) : to + visible: mainItem.isTransferring && value != 0 + /* Change format? Current is % + text: if(mainRow.contentModel){ + var fileSize = Utils.formatSize(mainRow.contentModel.fileSize) + return progressBar.visible + ? Utils.formatSize(mainRow.contentModel.fileOffset) + '/' + fileSize + : fileSize + }else + return '' + */ + } + } + Text { + id: fileName + Layout.fillWidth: true + Layout.fillHeight: true + visible: mainItem.contentModel && !mainItem.isAnimatedImage + + color: FileViewStyle.extension.text.colorModel.color + font.pointSize: FileViewStyle.name.pointSize + wrapMode: Text.WrapAnywhere + horizontalAlignment: Text.AlignHCenter + maximumLineCount: 2 + + text: mainItem.name + } + Text{ + id: downloadText + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredHeight: visible ? contentHeight : 0 + //: 'Cancel' : Message link to cancel a transfer (upload/download) + text: mainItem.contentModel ? mainItem.isTransferring ? qsTr('fileTransferCancel') + //: 'Download' : Message link to download a file + : qsTr('fileTransferDownload') +' ('+Utils.formatSize(mainItem.contentModel.fileSize)+')' + : '' + font.underline: true + font.pointSize: FileViewStyle.download.pointSize + color:FileViewStyle.extension.text.colorModel.color + visible: (mainItem.contentModel? (!mainItem.isOutgoing && !mainItem.contentModel.wasDownloaded) || mainItem.isTransferring : false) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } } + } } + Loader { id: thumbnailProvider - anchors.fill: parent - - sourceComponent: (mainItem.active ? (mainItem.thumbnail ? thumbnailImage : extension ): undefined) - - ScaleAnimator { - id: thumbnailProviderAnimator - - target: mainItem - - duration: ChatStyle.entry.message.file.animation.duration - easing.type: Easing.InOutQuad - from: 1.0 - } - + sourceComponent: (mainItem.contentModel ? + (mainItem.isAnimatedImage ? animatedImage + : (mainItem.haveThumbnail ? thumbnailImage : extension ) + ) : undefined) + states: State { name: 'hovered' } - - transitions: [ - Transition { - from: '' - to: 'hovered' - - ScriptAction { - script: { - if (thumbnailProviderAnimator.running) { - thumbnailProviderAnimator.running = false - } - - mainItem.z = Constants.zPopup - thumbnailProviderAnimator.to = mainItem.animationScale - thumbnailProviderAnimator.running = true - } - } - }, - Transition { - from: 'hovered' - to: '' - - ScriptAction { - script: { - if (thumbnailProviderAnimator.running) { - thumbnailProviderAnimator.running = false - } - - thumbnailProviderAnimator.to = 1.0 - thumbnailProviderAnimator.running = true - mainItem.z = 0 - } - } - } - ] } - MouseArea { - function handleMouseMove (mouse) { - thumbnailProvider.state = Utils.pointIsInItem(this, thumbnailProvider, mouse) - ? 'hovered' - : '' - } + Loader { + id: waitingProvider anchors.fill: parent - - onClicked: { - clickOnFile() - thumbnailProvider.state = '' + sourceComponent: thumbnailProvider.sourceComponent == thumbnailImage && thumbnailProvider.item.status != Image.Ready + ? extension + : undefined + states: State { + name: 'hovered' } - onExited: thumbnailProvider.state = '' - onMouseXChanged: handleMouseMove.call(this, mouse) - onMouseYChanged: handleMouseMove.call(this, mouse) } } \ No newline at end of file diff --git a/linphone-app/ui/modules/Linphone/Styles/Chat/ChatStyle.qml b/linphone-app/ui/modules/Linphone/Styles/Chat/ChatStyle.qml index e84240bba..ee59bc91c 100644 --- a/linphone-app/ui/modules/Linphone/Styles/Chat/ChatStyle.qml +++ b/linphone-app/ui/modules/Linphone/Styles/Chat/ChatStyle.qml @@ -201,7 +201,17 @@ QtObject { property var outgoingColor: ColorsList.addImageColor(sectionName+'_download_out', icon, 'g') property var incomingColor: ColorsList.addImageColor(sectionName+'_download_in', icon, 'q') } - + property QtObject thumbnailVideoIcon: QtObject { + property int iconSize: 40 + property string name : 'play' + property string icon : 'thumbnail_video_custom' + property var backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'wr_n_b_bg') + property var backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'wr_h_b_bg') + property var backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'wr_p_b_bg') + property var foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'wr_n_b_fg') + property var foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'wr_h_b_fg') + property var foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'wr_p_b_fg') + } property QtObject animation: QtObject { property int duration: 300 property real to: 1.7 @@ -212,6 +222,7 @@ QtObject { property string icon: 'file_extension_custom' property string unknownIcon: 'file_unknown_custom' property int iconSize: 60 + property int internalSize: 37 property int radius: 0 property QtObject background: QtObject { diff --git a/linphone-app/ui/modules/Linphone/Styles/File/FileViewStyle.qml b/linphone-app/ui/modules/Linphone/Styles/File/FileViewStyle.qml new file mode 100644 index 000000000..bd82d5c74 --- /dev/null +++ b/linphone-app/ui/modules/Linphone/Styles/File/FileViewStyle.qml @@ -0,0 +1,82 @@ +pragma Singleton +import QtQml 2.2 + +import Units 1.0 +import ColorsList 1.0 + +// ============================================================================= + +QtObject { + property string sectionName : 'FileView' + + property int height: 120 + property int heightbetter: 200 + property int iconSize: 18 + property int margins: 8 + property int spacing: 8 + property int width: 100 + + property QtObject name: QtObject{ + property int pointSize: Units.dp * 7 + } + + property QtObject download: QtObject{ + property string icon: 'download_custom' + property int height: 20 + property int pointSize: Units.dp * 8 + property int iconSize: 30 + property var outgoingColor: ColorsList.addImageColor(sectionName+'_download_out', icon, 'g') + property var incomingColor: ColorsList.addImageColor(sectionName+'_download_in', icon, 'q') + } + property QtObject thumbnailVideoIcon: QtObject { + property int iconSize: 40 + property string name : 'play' + property string icon : 'thumbnail_video_custom' + property var backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'wr_n_b_bg') + property var backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'wr_h_b_bg') + property var backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'wr_p_b_bg') + property var foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'wr_n_b_fg') + property var foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'wr_h_b_fg') + property var foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'wr_p_b_fg') + } + property QtObject animation: QtObject { + property int duration: 300 + property real to: 1.7 + property real thumbnailTo: 2 + } + + property QtObject extension: QtObject { + property string icon: 'file_extension_custom' + property string unknownIcon: 'file_unknown_custom' + property int iconSize: 60 + property int internalSize: 37 + property int radius: 0 + + property QtObject background: QtObject { + property var colorModel: ColorsList.add(sectionName+'_file_extension_bg', 'q') + property var borderColorModel: ColorsList.add(sectionName+'_file_extension_border', 'extension_file_border') + } + + property QtObject text: QtObject { + property var colorModel: ColorsList.add(sectionName+'_file_extension_text', 'd') + property int pointSize: Units.dp * 9 + } + } + + property QtObject status: QtObject { + property int spacing: 4 + + property QtObject bar: QtObject { + property int height: 6 + property int radius: 3 + + property QtObject background: QtObject { + property var colorModel: ColorsList.add(sectionName+'_file_statusbar_bg', 'f') + } + + property QtObject contentItem: QtObject { + property var colorModel: ColorsList.add(sectionName+'_file_statusbar_content', 'p') + } + } + } +} diff --git a/linphone-app/ui/modules/Linphone/Styles/qmldir b/linphone-app/ui/modules/Linphone/Styles/qmldir index 98a4aa786..6e41d0f42 100644 --- a/linphone-app/ui/modules/Linphone/Styles/qmldir +++ b/linphone-app/ui/modules/Linphone/Styles/qmldir @@ -37,6 +37,7 @@ singleton OnlineInstallerDialogStyle 1.0 Dialog/OnlineInstallerDialogS singleton SipAddressDialogStyle 1.0 Dialog/SipAddressDialogStyle.qml singleton ZrtpTokenAuthenticationDialogStyle 1.0 Dialog/ZrtpTokenAuthenticationDialogStyle.qml +singleton FileViewStyle 1.0 File/FileViewStyle.qml singleton HistoryStyle 1.0 History/HistoryStyle.qml