Verified Commit 6d3d9828 authored by Filipe Azevedo's avatar Filipe Azevedo Committed by Linus Jahn

Refactor media previews

Both send media sheet and chat message now share preview code.

Task-Id: #287Signed-off-by: Linus Jahn's avatarLinus Jahn <lnj@kaidan.im>
parent 9fbb3ac3
......@@ -17,15 +17,18 @@
<file alias="qml/elements/RosterListItem.qml">src/qml/elements/RosterListItem.qml</file>
<file alias="qml/elements/MessageCounter.qml">src/qml/elements/MessageCounter.qml</file>
<file alias="qml/elements/ChatMessage.qml">src/qml/elements/ChatMessage.qml</file>
<file alias="qml/elements/ChatMessageImage.qml">src/qml/elements/ChatMessageImage.qml</file>
<file alias="qml/elements/RoundImage.qml">src/qml/elements/RoundImage.qml</file>
<file alias="qml/elements/IconButton.qml">src/qml/elements/IconButton.qml</file>
<file alias="qml/elements/FileChooser.qml">src/qml/elements/FileChooser.qml</file>
<file alias="qml/elements/FileChooserDesktop.qml">src/qml/elements/FileChooserDesktop.qml</file>
<file alias="qml/elements/FileChooserMobile.qml">src/qml/elements/FileChooserMobile.qml</file>
<file alias="qml/elements/SendMediaSheet.qml">src/qml/elements/SendMediaSheet.qml</file>
<file alias="qml/elements/MediaPreview.qml">src/qml/elements/MediaPreview.qml</file>
<file alias="qml/elements/MediaPreviewImage.qml">src/qml/elements/MediaPreviewImage.qml</file>
<file alias="qml/elements/MediaPreviewVideo.qml">src/qml/elements/MediaPreviewVideo.qml</file>
<file alias="qml/elements/MediaPreviewAudio.qml">src/qml/elements/MediaPreviewAudio.qml</file>
<file alias="qml/elements/MediaPreviewOther.qml">src/qml/elements/MediaPreviewOther.qml</file>
<file alias="qml/elements/MediaPreviewLoader.qml">src/qml/elements/MediaPreviewLoader.qml</file>
<file alias="qml/elements/EmojiPicker.qml">src/qml/elements/EmojiPicker.qml</file>
<file alias="qml/elements/TextAvatar.qml">src/qml/elements/TextAvatar.qml</file>
<file alias="qml/elements/Avatar.qml">src/qml/elements/Avatar.qml</file>
......
......@@ -90,24 +90,6 @@ static bool operator==(const QXmppMessage &left, const QXmppMessage &right) {
&& left.replaceId() == right.replaceId();
}
MessageType Message::mediaTypeFromMimeType(const QMimeType &type)
{
if (type.inherits("image/jpeg") || type.inherits("image/png") ||
type.inherits("image/gif"))
return MessageType::MessageImage;
if (type.inherits("audio/flac") || type.inherits("audio/mp4") ||
type.inherits("audio/ogg") || type.inherits("audio/wav") ||
type.inherits("audio/mpeg") || type.inherits("audio/webm"))
return MessageType::MessageAudio;
if (type.inherits("video/mpeg") || type.inherits("video/x-msvideo") ||
type.inherits("video/quicktime") || type.inherits("video/mp4") ||
type.inherits("video/x-matroska"))
return MessageType::MessageVideo;
if (type.inherits("text/plain"))
return MessageType::MessageDocument;
return MessageType::MessageFile;
}
bool Message::operator==(const Message &m) const
{
return ::operator==(static_cast<const QXmppMessage &>(m), static_cast<const QXmppMessage &>(*this))
......
......@@ -45,8 +45,6 @@ using namespace Enums;
class Message : public QXmppMessage
{
public:
static MessageType mediaTypeFromMimeType(const QMimeType&);
/**
* Compares another @c Message with this. Only attributes that are saved in the
* database are checked.
......
......@@ -44,6 +44,7 @@
#include "Message.h"
#include "MessageModel.h"
#include "Notifications.h"
#include "MediaUtils.h"
MessageHandler::MessageHandler(Kaidan *kaidan, QXmppClient *client, MessageModel *model,
QObject *parent)
......@@ -112,29 +113,35 @@ void MessageHandler::handleMessage(const QXmppMessage &msg)
// check if message contains a link and also check out of band url
QStringList bodyWords = message.body().split(" ");
bodyWords.prepend(msg.outOfBandUrl());
for (const QString &word : bodyWords) {
if (!word.startsWith("https://") && !word.startsWith("http://"))
for (const QString &word : qAsConst(bodyWords)) {
if (!MediaUtils::isHttp(word)) {
continue;
}
// check message type by file name in link
// This is hacky, but needed without SIMS or an additional HTTP request.
// Also, this can be useful when a user manually posts an HTTP url.
QUrl url(word);
const QList<QMimeType> mediaTypes =
QMimeDatabase().mimeTypesForFileName(url.fileName());
for (const QMimeType &type : mediaTypes) {
MessageType mType = Message::mediaTypeFromMimeType(type);
if (mType == MessageType::MessageImage ||
mType == MessageType::MessageAudio ||
mType == MessageType::MessageVideo ||
mType == MessageType::MessageDocument ||
mType == MessageType::MessageFile) {
message.setMediaType(mType);
message.setMediaContentType(type.name());
message.setOutOfBandUrl(url.toEncoded());
break;
}
const QUrl url(word);
const QMimeType mimeType = MediaUtils::mimeType(url);
const MessageType messageType = MediaUtils::messageType(mimeType);
switch (messageType) {
case MessageType::MessageImage:
case MessageType::MessageAudio:
case MessageType::MessageVideo:
case MessageType::MessageDocument:
case MessageType::MessageFile:
message.setMediaType(messageType);
message.setMediaContentType(mimeType.name());
message.setOutOfBandUrl(url.toEncoded());
break;
case MessageType::MessageText:
case MessageType::MessageGeoLocation:
case MessageType::MessageUnknown:
continue;
}
break; // we can only handle one link
}
......
......@@ -39,7 +39,6 @@
class Kaidan;
class MessageModel;
class QMimeType;
class QXmppMessage;
class QXmppDiscoveryIq;
class QXmppCarbonManager;
......
......@@ -33,7 +33,6 @@
#include "Kaidan.h"
#include "MessageDb.h"
// Qt 5
#include <QMimeType>
// QXmpp
#include <QXmppUtils.h>
......
......@@ -34,7 +34,6 @@
#include <QAbstractListModel>
#include "Message.h"
class QMimeType;
class MessageDb;
class Kaidan;
......
......@@ -33,11 +33,11 @@
#include "MessageHandler.h"
#include "RosterManager.h"
#include "TransferCache.h"
#include "MediaUtils.h"
// QXmpp
#include <QXmppUtils.h>
// Qt
#include <QMimeDatabase>
#include <QMimeType>
#include <QMutexLocker>
#include <QDateTime>
#include <QBuffer>
......@@ -80,16 +80,11 @@ void UploadManager::sendFile(const QString &jid, const QUrl &fileUrl, const QStr
qDebug() << "[client] [UploadManager] Adding upload for file:" << fileUrl;
QFileInfo file;
if (fileUrl.isLocalFile())
file = QFileInfo(fileUrl.toLocalFile());
else
// this is used for android's content:/image:-URLs
file = QFileInfo(fileUrl.toString());
// toString() is used for android's content:/image:-URLs
QFileInfo file(fileUrl.isLocalFile() ? fileUrl.toLocalFile() : fileUrl.toString());
const QXmppHttpUpload* upload = manager.uploadFile(file);
QMimeType mimeType = QMimeDatabase().mimeTypeForFile(file);
const QMimeType mimeType = MediaUtils::mimeType(fileUrl);
const MessageType messageType = MediaUtils::messageType(mimeType);
const QString msgId = QXmppUtils::generateStanzaHash(48);
auto *msg = new Message;
......@@ -98,7 +93,7 @@ void UploadManager::sendFile(const QString &jid, const QUrl &fileUrl, const QStr
msg->setId(msgId);
msg->setSentByMe(true);
msg->setBody(body);
msg->setMediaType(Message::mediaTypeFromMimeType(mimeType));
msg->setMediaType(messageType);
msg->setStamp(QDateTime::currentDateTimeUtc());
msg->setMediaSize(file.size());
msg->setMediaContentType(mimeType.name());
......
......@@ -33,7 +33,9 @@ import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.2 as Controls
import org.kde.kirigami 2.0 as Kirigami
import im.kaidan.kaidan 1.0
import MediaUtils 0.1
RowLayout {
id: root
......@@ -197,10 +199,8 @@ RowLayout {
Controls.ToolButton {
visible: {
mediaType !== Enums.MessageText &&
!isLoading &&
mediaLocation === "" &&
mediaGetUrl !== ""
(mediaType !== Enums.MessageType.MessageText && !isLoading && mediaGetUrl !== ""
&& (mediaLocation === "" || !MediaUtilsInstance.localFileAvailable(media.mediaSource)))
}
text: qsTr("Download")
onClicked: {
......@@ -209,22 +209,19 @@ RowLayout {
}
}
// media loader
Loader {
MediaPreviewLoader {
id: media
source: {
if (mediaType === Enums.MessageImage &&
mediaLocation !== "")
"ChatMessageImage.qml"
else
""
mediaSource: {
return root.mediaLocation !== ''
? MediaUtilsInstance.fromLocalFile(root.mediaLocation)
: root.mediaGetUrl
}
property string sourceUrl: "file://" + mediaLocation
Layout.maximumWidth: root.width - Kirigami.Units.gridUnit * 6
Layout.preferredHeight: item ? item.paintedHeight : 0
mediaSourceType: root.mediaType
showOpenButton: true
message: root
}
// message body
Controls.Label {
id: bodyLabel
......@@ -236,7 +233,7 @@ RowLayout {
: Kirigami.Theme.complementaryTextColor
onLinkActivated: Qt.openUrlExternally(link)
Layout.maximumWidth: mediaType === Enums.MessageImage && media.width !== 0
Layout.maximumWidth: media.enabled
? media.width
: root.width - Kirigami.Units.gridUnit * 6
}
......@@ -252,12 +249,6 @@ RowLayout {
}
// message meta: date, isDelivered
RowLayout {
// progress bar for upload/download status
Controls.ProgressBar {
visible: isLoading
value: upload ? upload.progress : 0
}
Controls.Label {
id: dateLabel
text: Qt.formatDateTime(dateTime, "dd. MMM yyyy, hh:mm")
......@@ -282,6 +273,15 @@ RowLayout {
Layout.preferredWidth: Kirigami.Units.gridUnit * 0.65
}
}
// progress bar for upload/download status
Controls.ProgressBar {
visible: isLoading
value: upload ? upload.progress : 0
Layout.fillWidth: true
Layout.maximumWidth: Kirigami.Units.gridUnit * 14
}
}
}
......
......@@ -28,16 +28,35 @@
* along with Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* This element is used in the @see SendMediaSheet to display information about a selected file to
* the user. It shows the file name, file size and a little file icon.
*/
import QtQuick 2.6
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0 as Controls
import org.kde.kirigami 2.0 as Kirigami
import im.kaidan.kaidan 1.0
Rectangle {
id: root
AnimatedImage {
id: image
property url mediaSource
property int mediaSourceType: Enums.MessageType.MessageUnknown
property bool showOpenButton: false
property Item message: null
property int messageSize: Kirigami.Units.gridUnit * 14
source: sourceUrl
fillMode: Image.PreserveAspectFit
color: message ? 'transparent' : Kirigami.Theme.backgroundColor
MouseArea {
anchors.fill: parent
onClicked: Qt.openUrlExternally(sourceUrl)
}
Layout.fillHeight: false
Layout.fillWidth: message ? false : true
Layout.alignment: Qt.AlignCenter
Layout.margins: 0
Layout.leftMargin: undefined
Layout.topMargin: undefined
Layout.rightMargin: undefined
Layout.bottomMargin: undefined
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* This element is used in the @see SendMediaSheet to display information about a selected audio file to
* the user. It just displays the audio in a rectangle.
*/
import QtQuick 2.6
import QtQuick 2.6
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0 as Controls
import QtMultimedia 5.9 as Multimedia
import org.kde.kirigami 2.0 as Kirigami
import MediaUtils 0.1
MediaPreview {
id: root
property alias placeHolder: placeHolder.data
readonly property Multimedia.MediaPlayer player: mediaPlayer
readonly property Controls.Button playPauseButton: playPause
color: 'transparent'
Layout.preferredHeight: message ? Kirigami.Units.gridUnit * 2.4 : Kirigami.Units.gridUnit * 2.45
Layout.preferredWidth: message ? Kirigami.Units.gridUnit * 10 : Kirigami.Units.gridUnit * 20
Layout.maximumWidth: message ? messageSize : -1
Multimedia.MediaPlayer {
id: mediaPlayer
source: root.mediaSource
volume: volumePlayer.volume
onStopped: seek(0)
}
ColumnLayout {
anchors {
fill: parent
}
Item {
id: placeHolder
visible: children.length > 0
Layout.fillHeight: true
Layout.fillWidth: true
}
RowLayout {
Layout.topMargin: 6
Controls.ToolButton {
id: playPause
icon.name: mediaPlayer.playbackState === Multimedia.MediaPlayer.PlayingState
? 'media-playback-pause'
: 'media-playback-start'
onClicked: {
switch (mediaPlayer.playbackState) {
case Multimedia.MediaPlayer.PlayingState:
mediaPlayer.pause()
break
case Multimedia.MediaPlayer.PausedState:
case Multimedia.MediaPlayer.StoppedState:
mediaPlayer.play()
break
}
}
}
Controls.Slider {
from: 0
to: mediaPlayer.duration
value: mediaPlayer.position
enabled: mediaPlayer.seekable
Layout.fillWidth: true
Row {
anchors {
right: parent.right
top: parent.top
topMargin: -(parent.height / 2)
}
readonly property real fontSize: 7
Controls.Label {
text: MediaUtilsInstance.prettyDuration(mediaPlayer.position, mediaPlayer.duration)
font.pointSize: parent.fontSize
visible: mediaPlayer.duration > 0 && mediaPlayer.playbackState !== Multimedia.MediaPlayer.StoppedState
}
Controls.Label {
text: ' / '
font.pointSize: parent.fontSize
visible: mediaPlayer.duration > 0 && mediaPlayer.playbackState !== Multimedia.MediaPlayer.StoppedState
}
Controls.Label {
text: MediaUtilsInstance.prettyDuration(mediaPlayer.duration)
visible: mediaPlayer.duration > 0
font.pointSize: parent.fontSize
}
}
onMoved: mediaPlayer.seek(value)
}
Controls.Slider {
id: volumePlayer
readonly property real volume: Multimedia.QtMultimedia.convertVolume(value,
Multimedia.QtMultimedia.LogarithmicVolumeScale,
Multimedia.QtMultimedia.LinearVolumeScale)
from: 0
to: 1.0
value: 1.0
visible: !root.message && !Kirigami.Settings.isMobile
Layout.preferredWidth: root.width / 6
}
Controls.ToolButton {
visible: root.showOpenButton
icon {
name: 'document-open'
}
onClicked: Qt.openUrlExternally(root.mediaSource)
}
}
}
}
......@@ -34,22 +34,37 @@
*/
import QtQuick 2.6
import QtQuick.Controls 2.0 as Controls
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0 as Controls
import org.kde.kirigami 2.0 as Kirigami
Rectangle {
color: Kirigami.Theme.backgroundColor
MediaPreview {
id: root
Layout.preferredHeight: message ? messageSize : Kirigami.Units.gridUnit * 18
Layout.preferredWidth: Kirigami.Units.gridUnit * 32
Layout.preferredHeight: Kirigami.Units.gridUnit * 18
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: message ? messageSize : -1
AnimatedImage {
id: image
Image {
anchors.fill: parent
source: sourceUrl
fillMode: Image.PreserveAspectFit
asynchronous: true // image might be very large
mipmap: true
source: root.mediaSource
anchors {
fill: parent
}
MouseArea {
enabled: root.showOpenButton
anchors {
fill: parent
}
onClicked: Qt.openUrlExternally(root.mediaSource)
}
}
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.6
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0 as Controls
import QtPositioning 5.9 as Positioning
import im.kaidan.kaidan 1.0
import MediaUtils 0.1
Loader {
id: root
property url mediaSource
property int mediaSourceType
property bool showOpenButton
property QtObject message
property QtObject mediaSheet
enabled: {
switch (mediaSourceType) {
case Enums.MessageType.MessageUnknown:
case Enums.MessageType.MessageText:
case Enums.MessageType.MessageGeoLocation:
return false
case Enums.MessageType.MessageImage:
case Enums.MessageType.MessageAudio:
case Enums.MessageType.MessageVideo:
case Enums.MessageType.MessageFile:
case Enums.MessageType.MessageDocument:
return mediaSource != '' && sourceComponent !== null
}
}
visible: enabled
sourceComponent: {
switch (mediaSourceType) {
case Enums.MessageType.MessageUnknown:
case Enums.MessageType.MessageText:
case Enums.MessageType.MessageGeoLocation:
return null
case Enums.MessageType.MessageImage:
return imagePreview
case Enums.MessageType.MessageAudio:
return audioPreview
case Enums.MessageType.MessageVideo: