From 703931a5a11f6ee3dbab13f7378c66265e188ba5 Mon Sep 17 00:00:00 2001 From: John Gibbon Date: Sun, 17 Jan 2021 21:19:29 +0100 Subject: [PATCH] Add TDLibImage/TDLibThumbnail; rework Audio/VoiceNote/Document also, again, a quick Location fix before its real turn --- harbour-fernschreiber.pro | 3 + images/icon-m-copy-to-folder.svg | 39 ++ qml/components/TDLibImage.qml | 55 ++ qml/components/TDLibThumbnail.qml | 129 +++++ .../messageContent/MessageAudio.qml | 486 ++---------------- .../MessageContentFileInfoBase.qml | 201 ++++++++ .../messageContent/MessageDocument.qml | 139 ++--- .../messageContent/MessageLocation.qml | 2 +- .../messageContent/MessageVoiceNote.qml | 9 +- qml/js/functions.js | 2 +- qml/pages/ChatPage.qml | 3 +- src/tdlibfile.cpp | 10 + src/tdlibfile.h | 1 + src/tdlibwrapper.cpp | 31 ++ src/tdlibwrapper.h | 3 + translations/harbour-fernschreiber-de.ts | 22 +- translations/harbour-fernschreiber-en.ts | 22 +- translations/harbour-fernschreiber-es.ts | 24 +- translations/harbour-fernschreiber-fi.ts | 26 +- translations/harbour-fernschreiber-hu.ts | 22 +- translations/harbour-fernschreiber-it.ts | 22 +- translations/harbour-fernschreiber-pl.ts | 22 +- translations/harbour-fernschreiber-ru.ts | 22 +- translations/harbour-fernschreiber-sv.ts | 22 +- translations/harbour-fernschreiber-zh_CN.ts | 24 +- translations/harbour-fernschreiber.ts | 22 +- 26 files changed, 666 insertions(+), 697 deletions(-) create mode 100644 images/icon-m-copy-to-folder.svg create mode 100644 qml/components/TDLibImage.qml create mode 100644 qml/components/TDLibThumbnail.qml create mode 100644 qml/components/messageContent/MessageContentFileInfoBase.qml diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro index 13a2268..82b4a20 100644 --- a/harbour-fernschreiber.pro +++ b/harbour-fernschreiber.pro @@ -61,6 +61,8 @@ DISTFILES += qml/harbour-fernschreiber.qml \ qml/components/ReplyMarkupButtons.qml \ qml/components/StickerPicker.qml \ qml/components/PhotoTextsListItem.qml \ + qml/components/TDLibImage.qml \ + qml/components/TDLibThumbnail.qml \ qml/components/VoiceNoteOverlay.qml \ qml/components/chatInformationPage/ChatInformationEditArea.qml \ qml/components/chatInformationPage/ChatInformationPageContent.qml \ @@ -91,6 +93,7 @@ DISTFILES += qml/harbour-fernschreiber.qml \ qml/components/messageContent/MessageAnimation.qml \ qml/components/messageContent/MessageAudio.qml \ qml/components/messageContent/MessageContentBase.qml \ + qml/components/messageContent/MessageContentFileInfoBase.qml \ qml/components/messageContent/MessageDocument.qml \ qml/components/messageContent/MessageGame.qml \ qml/components/messageContent/MessageLocation.qml \ diff --git a/images/icon-m-copy-to-folder.svg b/images/icon-m-copy-to-folder.svg new file mode 100644 index 0000000..71f0b9c --- /dev/null +++ b/images/icon-m-copy-to-folder.svg @@ -0,0 +1,39 @@ + +image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/qml/components/TDLibImage.qml b/qml/components/TDLibImage.qml new file mode 100644 index 0000000..5128143 --- /dev/null +++ b/qml/components/TDLibImage.qml @@ -0,0 +1,55 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf and other contributors + + This file is part of Fernschreiber. + + Fernschreiber 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. + + Fernschreiber 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 Fernschreiber. If not, see . +*/ +import QtQuick 2.6 +import WerkWolf.Fernschreiber 1.0 +import Sailfish.Silica 1.0 + +import "../js/debug.js" as Debug + +Image { + id: tdLibImage + property alias fileInformation: file.fileInformation + readonly property alias file: file + property bool highlighted + + asynchronous: true + enabled: !!file.fileId + fillMode: Image.PreserveAspectCrop + clip: true + opacity: status === Image.Ready ? 1.0 : 0.0 + source: enabled && file.isDownloadingCompleted ? file.path : "" + visible: opacity > 0 + sourceSize { + width: width + height: height + } + + Behavior on opacity { FadeAnimation {} } + + layer { + enabled: tdLibImage.enabled && tdLibImage.highlighted + effect: PressEffect { source: tdLibImage } + } + + TDLibFile { + id: file + autoLoad: true + tdlib: tdLibWrapper + } +} diff --git a/qml/components/TDLibThumbnail.qml b/qml/components/TDLibThumbnail.qml new file mode 100644 index 0000000..8454dd7 --- /dev/null +++ b/qml/components/TDLibThumbnail.qml @@ -0,0 +1,129 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf and other contributors + + This file is part of Fernschreiber. + + Fernschreiber 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. + + Fernschreiber 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 Fernschreiber. If not, see . +*/ +import QtQuick 2.6 +import Sailfish.Silica 1.0 +import Nemo.Thumbnailer 1.0 + +Item { + id: tdlibThumbnail + /* + Optional thumbnail, usually as property "thumbnail". + The following TDLib objects can have it: + - animation + - audio (as "album_cover_thumbnail") + - document + - sticker (no minithumbnail) + - video + - videoNote + - stickerSet (no minithumbnail) + - stickerSetInfo (no minithumbnail) + - inlineQueryResultArticle (no minithumbnail) + - inlineQueryResultContact (no minithumbnail) + - inlineQueryResultLocation (no minithumbnail) + - inlineQueryResultVenue (no minithumbnail) + */ + property var thumbnail + /* + Optional minithumbnail, usually as property "minithumbnail". + Has data inline: If present, it doesn't need another request. + The following TDLib objects can have it: + - animation + - audio (as "album_cover_minithumbnail") + - document + - photo / chatPhoto (Note: No thumbnail, so not applicable here) + - video + - videoNote + */ + property var minithumbnail + property bool useBackgroundImage: true + property bool highlighted + + property bool isVideo: !!thumbnail && thumbnail.format["@type"] === "thumbnailFormatMpeg4" + property string videoMimeType: "video/mp4" + + readonly property bool hasVisibleThumbnail: thumbnailImage.opacity !== 1.0 + && !(videoThumbnailLoader.item && videoThumbnailLoader.item.opacity === 1.0) + + layer { + enabled: highlighted + effect: PressEffect { source: tdlibThumbnail } + } + + Loader { + id: backgroundLoader + anchors.fill: parent + active: !parent.hasVisibleThumbnail + asynchronous: true + sourceComponent: !!parent.minithumbnail ? miniThumbnailComponent : parent.useBackgroundImage ? backgroundImageComponent : "" + Component { + id: backgroundImageComponent + BackgroundImage {} + } + Component { + id: miniThumbnailComponent + Image { + clip: true + asynchronous: true + fillMode: Image.PreserveAspectCrop + opacity: status === Image.Ready ? 1.0 : 0.0 + smooth: false + source: "data:image/jpg;base64," + tdlibThumbnail.miniThumbnail.data + visible: opacity > 0 + Behavior on opacity { FadeAnimation {} } + } + } + } + + // image thumbnail + TDLibImage { + id: thumbnailImage + anchors.fill: parent + enabled: !parent.isVideo + fileInformation: tdlibThumbnail.thumbnail ? tdlibThumbnail.thumbnail.file : {} + onStatusChanged: { //TODO check if this is really how it is ;) + if(status === Image.Error) { + // in some cases, webp is used (without correct mime type). + // we just try it blindly and cross our fingers: + tdlibThumbnail.videoMimeType = "image/webp"; + tdlibThumbnail.isVideo = true; + } + } + } + + // Fallback for video thumbnail format: try to use Nemo.Thumbnailer + Loader { + id: videoThumbnailLoader + active: parent.isVideo + asynchronous: true + anchors.fill: parent + sourceComponent: Component { + id: videoThumbnail + Thumbnail { + id: thumbnail + source: thumbnailImage.file.path + sourceSize.width: width + sourceSize.height: height + mimeType: tdlibThumbnail.videoMimeType + visible: opacity > 0 + opacity: status === Thumbnail.Ready ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + } + } + } +} diff --git a/qml/components/messageContent/MessageAudio.qml b/qml/components/messageContent/MessageAudio.qml index 38d6de6..7f4c55a 100644 --- a/qml/components/messageContent/MessageAudio.qml +++ b/qml/components/messageContent/MessageAudio.qml @@ -20,455 +20,77 @@ import QtQuick 2.6 import Sailfish.Silica 1.0 import QtMultimedia 5.6 import "../" +import "../../js/twemoji.js" as Emoji import "../../js/functions.js" as Functions import "../../js/debug.js" as Debug -MessageContentBase { - id: audioMessageComponent +MessageContentFileInfoBase { + id: contentItem - property var audioData: ( rawMessage.content['@type'] === "messageVoiceNote" ) ? rawMessage.content.voice_note : ( ( rawMessage.content['@type'] === "messageAudio" ) ? rawMessage.content.audio : ""); - property string audioUrl; - property int previewFileId; - property int audioFileId; - property bool onScreen: messageListItem ? messageListItem.page.status === PageStatus.Active : true - property string audioType : "voiceNote"; + fileInformation: rawMessage.content.audio.audio + thumbnail: rawMessage.content.audio.album_cover_thumbnail + minithumbnail: rawMessage.content.audio.album_cover_minithumbnail - height: width / 2 + primaryText: Emoji.emojify(rawMessage.content.audio.performer, primaryLabel.font.pixelSize) + secondaryText: Emoji.emojify(rawMessage.content.audio.title, secondaryLabel.font.pixelSize) + tertiaryLabel.visible: (duration || (audioPlayer.duration/1000)) > 0 + tertiaryText: (audioPlayer.position > 0 || audioPlayer.playbackState === Audio.PlayingState ? (Format.formatDuration(audioPlayer.position/1000, Formatter.DurationShort)+" / ") : "") + Format.formatDuration(contentItem.duration > 0 ? contentItem.duration : (audioPlayer.duration/1000), Formatter.DurationShort) - function getTimeString(rawSeconds) { - var minutes = Math.floor( rawSeconds / 60 ); - var seconds = rawSeconds - ( minutes * 60 ); - - if ( minutes < 10 ) { - minutes = "0" + minutes; - } - if ( seconds < 10 ) { - seconds = "0" + seconds; - } - return minutes + ":" + seconds; - } - - Component.onCompleted: { - updateAudioThumbnail(); - } - - function updateAudioThumbnail() { - if (audioData) { - audioType = ( audioData['@type'] === "voiceNote" ) ? "voice" : "audio"; - audioFileId = audioData[audioType].id; - if (typeof audioData.album_cover_thumbnail !== "undefined") { - previewFileId = audioData.album_cover_thumbnail.file.id; - if (audioData.album_cover_thumbnail.file.local.is_downloading_completed) { - placeholderImage.source = audioData.album_cover_thumbnail.file.local.path; + leftButton { + icon.source: audioPlayer.playbackState === Audio.PlayingState || (file.isDownloadingActive && audioPlayer.autoPlay) ? "image://theme/icon-m-pause": "image://theme/icon-m-play" + onClicked: { + if(!file.isDownloadingCompleted && !file.isDownloadingActive) { + file.load(); + audioPlayer.autoPlay = true; + } else if(file.isDownloadingActive) { + audioPlayer.autoPlay = false; + file.cancel(); + } else if(file.isDownloadingCompleted) { + //playPause + if(audioPlayer.playbackState === Audio.PlayingState) { + audioPlayer.pause(); } else { - tdLibWrapper.downloadFile(previewFileId); - } - } else { - placeholderImage.source = "image://theme/icon-l-music?white"; - placeholderImage.width = Theme.itemSizeLarge - placeholderImage.height = Theme.itemSizeLarge - } - } - } - - function handlePlay() { - if (audioData[audioType].local.is_downloading_completed) { - audioUrl = audioData[audioType].local.path; - audioComponentLoader.active = true; - } else { - audioDownloadBusyIndicator.running = true; - tdLibWrapper.downloadFile(audioFileId); - } - } - - Connections { - target: tdLibWrapper - onFileUpdated: { - if (typeof audioData === "object") { - if (fileInformation.local.is_downloading_completed) { - if (fileId === previewFileId) { - audioData.album_cover_thumbnail.file = fileInformation; - placeholderImage.source = fileInformation.local.path; - } - if (fileId === audioFileId) { - audioDownloadBusyIndicator.running = false; - audioData[audioType] = fileInformation; - audioUrl = fileInformation.local.path; - if (onScreen) { - audioComponentLoader.active = true; - } - } - } - if (fileId === audioFileId) { - downloadingProgressBar.maximumValue = fileInformation.size; - downloadingProgressBar.value = fileInformation.local.downloaded_size; + audioPlayer.play(); } } } + } - Image { - id: placeholderImage + property int duration: rawMessage.content.audio.duration + + Audio { + id: audioPlayer + source: file.isDownloadingCompleted ? file.path : "" + autoPlay: false + } + + Slider { width: parent.width - height: parent.height - anchors.centerIn: parent - asynchronous: true - fillMode: Image.PreserveAspectCrop - visible: status === Image.Ready ? true : false - layer.enabled: audioMessageComponent.highlighted - layer.effect: PressEffect { source: placeholderImage } - } - - BackgroundImage { - id: backgroundImage - visible: placeholderImage.status !== Image.Ready - layer.enabled: audioMessageComponent.highlighted - layer.effect: PressEffect { source: backgroundImage } - } - - Rectangle { - id: placeholderBackground - color: "black" - opacity: 0.3 - height: parent.height - width: parent.width - visible: playButton.visible - } - Label { - visible: !!(audioData.performer || audioData.title) - color: placeholderBackground.visible ? "white" : Theme.secondaryHighlightColor - wrapMode: Text.Wrap anchors { - fill: placeholderBackground - margins: Theme.paddingSmall + left: parent.left + leftMargin: -Screen.width/16 + right: parent.right + rightMargin: -Screen.width/16 + top: primaryItem.bottom + topMargin: -height/3 } - text: audioData.performer + (audioData.performer && audioData.title ? " - " : "") + audioData.title - font.pixelSize: Theme.fontSizeTiny - } + minimumValue: 0 + maximumValue: audioPlayer.duration ? audioPlayer.duration : 0.1 + stepSize: 1 + value: audioPlayer.position + enabled: audioPlayer.seekable + visible: file.isDownloadingCompleted && audioPlayer.playbackState === Audio.PlayingState || audioPlayer.playbackState === Audio.PausedState + opacity: visible ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + height: visible ? implicitHeight : 0 + Behavior on height { NumberAnimation { duration: 200 } } - Column { - width: parent.width - height: downloadingProgressBar.height + audioControlRow.height - anchors.centerIn: parent - Row { - id: audioControlRow - width: parent.width - height: Theme.iconSizeLarge - Item { - height: Theme.iconSizeLarge - width: downloadItem.visible ? parent.width / 2 : parent.width - IconButton { - id: playButton - anchors.centerIn: parent - width: Theme.iconSizeLarge - height: Theme.iconSizeLarge - icon { - source: "image://theme/icon-l-play?white" - asynchronous: true - } - highlighted: audioMessageComponent.highlighted || down - visible: placeholderImage.status === Image.Ready ? true : false - onClicked: { - handlePlay(); - } - } - BusyIndicator { - id: audioDownloadBusyIndicator - running: false - visible: running - anchors.centerIn: parent - size: BusyIndicatorSize.Large - } - } - Item { - id: downloadItem - width: parent.width / 2 - height: Theme.iconSizeLarge - visible: audioData[audioType].local.is_downloading_completed - Rectangle { - color: Theme.primaryColor - opacity: Theme.opacityFaint - width: Theme.iconSizeLarge * 0.9 - height: Theme.iconSizeLarge * 0.9 - anchors.centerIn: parent - radius: width / 2 - } - - IconButton { - id: downloadButton - anchors.centerIn: parent - width: Theme.iconSizeLarge - height: Theme.iconSizeLarge - icon { - source: "image://theme/icon-m-cloud-download?white" - asynchronous: true - } - highlighted: audioMessageComponent.highlighted || down - onClicked: { - tdLibWrapper.copyFileToDownloads(audioData[audioType].local.path); - } - } - } - } - ProgressBar { - id: downloadingProgressBar - minimumValue: 0 - maximumValue: 100 - value: 0 - visible: audioDownloadBusyIndicator.visible - width: parent.width + highlighted: contentItem.highlighted || down + onReleased: { + audioPlayer.seek(Math.floor(value)); + audioPlayer.play(); } } - - - Rectangle { - id: audioErrorShade - width: parent.width - height: parent.height - color: "lightgrey" - visible: placeholderImage.status === Image.Error ? true : false - opacity: 0.3 - } - - Rectangle { - id: errorTextOverlay - color: "black" - opacity: 0.8 - width: parent.width - height: parent.height - visible: false - } - - Text { - id: errorText - visible: false - width: parent.width - color: Theme.primaryColor - font.pixelSize: Theme.fontSizeExtraSmall - horizontalAlignment: Text.AlignHCenter - anchors { - verticalCenter: parent.verticalCenter - } - wrapMode: Text.Wrap - text: "" - } - - Loader { - id: audioComponentLoader - active: false - width: parent.width - height: parent.height - sourceComponent: audioComponent - } - - Component { - id: audioComponent - - Item { - width: parent ? parent.width : 0 - height: parent ? parent.height : 0 - - Connections { - target: messageAudio - onPlaying: { - playButton.visible = false; - downloadItem.visible = false; - } - } - - Connections { - target: audioMessageComponent - onClicked: { - if (messageAudio.playbackState === MediaPlayer.PlayingState) { - messageAudio.pause(); - timeLeftItem.visible = true; - } else { - messageAudio.play(); - } - } - } - - Audio { - id: messageAudio - - Component.onCompleted: { - if (messageAudio.error === MediaPlayer.NoError) { - messageAudio.play(); - } else { - errorText.text = qsTr("Error loading audio! " + messageAudio.errorString) - errorTextOverlay.visible = true; - errorText.visible = true; - } - } - - onStatusChanged: { - if (status == MediaPlayer.NoMedia) { - Debug.log("No Media"); - audioBusyIndicator.visible = false; - } - if (status == MediaPlayer.Loading) { - Debug.log("Loading"); - audioBusyIndicator.visible = true; - } - if (status == MediaPlayer.Loaded) { - Debug.log("Loaded"); - audioBusyIndicator.visible = false; - } - if (status == MediaPlayer.Buffering) { - Debug.log("Buffering"); - audioBusyIndicator.visible = true; - } - if (status == MediaPlayer.Stalled) { - Debug.log("Stalled"); - audioBusyIndicator.visible = true; - } - if (status == MediaPlayer.Buffered) { - Debug.log("Buffered"); - audioBusyIndicator.visible = false; - } - if (status == MediaPlayer.EndOfMedia) { - Debug.log("End of Media"); - audioBusyIndicator.visible = false; - } - if (status == MediaPlayer.InvalidMedia) { - Debug.log("Invalid Media"); - audioBusyIndicator.visible = false; - } - if (status == MediaPlayer.UnknownStatus) { - Debug.log("Unknown Status"); - audioBusyIndicator.visible = false; - } - } - - source: audioUrl - - onStopped: { - playButton.visible = true; - downloadItem.visible = true; - audioComponentLoader.active = false; - } - } - - BusyIndicator { - id: audioBusyIndicator - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: false - running: visible - size: BusyIndicatorSize.Medium - } - - Item { - id: timeLeftItem - width: parent.width - height: parent.height - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - opacity: visible ? 1 : 0 - Behavior on opacity { NumberAnimation {} } - - Rectangle { - id: positionTextOverlay - color: "black" - opacity: 0.3 - width: parent.width - height: parent.height - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - visible: pausedRow.visible - } - - Row { - id: pausedRow - width: parent.width - height: parent.height - ( messageAudioSlider.visible ? messageAudioSlider.height : 0 ) - ( positionText.visible ? positionText.height : 0 ) - visible: audioComponentLoader.active && messageAudio.playbackState === MediaPlayer.PausedState - Item { - height: parent.height - width: parent.width / 2 - IconButton { - id: pausedPlayButton - anchors.centerIn: parent - width: Theme.iconSizeLarge - height: Theme.iconSizeLarge - highlighted: audioMessageComponent.highlighted || down - icon { - asynchronous: true - source: "image://theme/icon-l-play?white" - } - onClicked: { - messageAudio.play(); - } - } - } - Item { - id: pausedDownloadItem - width: parent.width / 2 - height: parent.height - Rectangle { - color: Theme.primaryColor - opacity: Theme.opacityFaint - width: Theme.iconSizeLarge * 0.9 - height: Theme.iconSizeLarge * 0.9 - anchors.centerIn: parent - radius: width / 2 - } - - IconButton { - id: pausedDownloadButton - anchors.centerIn: parent - width: Theme.iconSizeLarge - height: Theme.iconSizeLarge - icon { - source: "image://theme/icon-m-cloud-download?white" - asynchronous: true - } - highlighted: audioMessageComponent.highlighted || down - onClicked: { - tdLibWrapper.copyFileToDownloads(audioData[audioType].local.path); - } - } - } - } - - Slider { - id: messageAudioSlider - width: parent.width - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: positionText.top - minimumValue: 0 - maximumValue: messageAudio.duration ? messageAudio.duration : 0.1 - stepSize: 1 - value: messageAudio.position - enabled: messageAudio.seekable - visible: (messageAudio.duration > 0) - highlighted: audioMessageComponent.highlighted || down - onReleased: { - messageAudio.seek(Math.floor(value)); - messageAudio.play(); - } - valueText: getTimeString(Math.round((messageAudio.duration - messageAudioSlider.value) / 1000)) - } - - Text { - id: positionText - visible: messageAudio.duration === 0 - color: Theme.primaryColor - font.pixelSize: Theme.fontSizeTiny - anchors { - bottom: parent.bottom - bottomMargin: Theme.paddingSmall - horizontalCenter: positionTextOverlay.horizontalCenter - } - wrapMode: Text.Wrap - text: ( messageAudio.duration - messageAudio.position ) > 0 ? getTimeString(Math.round((messageAudio.duration - messageAudio.position) / 1000)) : "-:-" - } - } - - } - - - } - } diff --git a/qml/components/messageContent/MessageContentFileInfoBase.qml b/qml/components/messageContent/MessageContentFileInfoBase.qml new file mode 100644 index 0000000..11ed18a --- /dev/null +++ b/qml/components/messageContent/MessageContentFileInfoBase.qml @@ -0,0 +1,201 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf and other contributors + + This file is part of Fernschreiber. + + Fernschreiber 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. + + Fernschreiber 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 Fernschreiber. If not, see . +*/ +import QtQuick 2.6 +import Sailfish.Silica 1.0 +import QtMultimedia 5.6 +import WerkWolf.Fernschreiber 1.0 +import QtGraphicalEffects 1.0 +import "../" +import "../../js/functions.js" as Functions +import "../../js/twemoji.js" as Emoji +import "../../js/debug.js" as Debug + +MessageContentBase { + id: contentItem + height: childrenRect.height + property alias fileInformation: file.fileInformation + property alias primaryLabel: primaryLabel + property alias primaryText: primaryLabel.text + property alias secondaryLabel: secondaryLabel + property alias secondaryText: secondaryLabel.text + property alias tertiaryLabel: tertiaryLabel + property alias tertiaryText: tertiaryLabel.text + property var thumbnail + property var minithumbnail + + readonly property alias file: file + readonly property alias primaryItem: primaryItem + readonly property alias leftButton: leftButton + readonly property alias labelsColumn: labelsColumn + readonly property alias copyButton: copyButton +// readonly property alias downloadNeededIndicatorIcon: downloadNeededIndicatorIcon + + TDLibFile { + id: file + tdlib: tdLibWrapper + autoLoad: false + } + + Item { + id: primaryItem + width: parent.width + height: Theme.itemSizeLarge + Loader { + active: contentItem.thumbnail || contentItem.minithumbnail + visible: active + anchors.fill: leftButton + sourceComponent: Component { + TDLibThumbnail { + opacity: 0.3 + thumbnail: contentItem.thumbnail + minithumbnail: contentItem.minithumbnail + } + } + } + + IconButton { + id: leftButton + highlighted: down || contentItem.highlighted + anchors.verticalCenter: parent.verticalCenter + icon { + asynchronous: true + } + + ProgressCircle { + value: file.downloadedSize / file.expectedSize + progressColor: Theme.highlightColor + backgroundColor: Theme.highlightDimmerColor + width: Theme.iconSizeMedium + height: Theme.iconSizeMedium + visible: opacity > 0 + opacity: file.isDownloadingActive ? 1.0 : 0.0 + anchors.centerIn: parent + Behavior on opacity { FadeAnimation {} } + } + Rectangle { + anchors.centerIn: downloadNeededIndicatorIcon + width: downloadNeededIndicatorIcon.width + Theme.paddingMedium + height: width + + color: Theme.rgba(Theme.overlayBackgroundColor, 0.2) + opacity: file.isDownloadingActive ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + visible: opacity > 0 + radius: width/2 + } + + Icon { + id: downloadNeededIndicatorIcon + source: file.isDownloadingActive || file.isDownloadingCompleted ? "image://theme/icon-s-clear-opaque-cross" : "image://theme/icon-s-cloud-download" + asynchronous: true + width: Theme.iconSizeExtraSmall + height: width + visible: opacity > 0 + sourceSize.width: width + sourceSize.height: height + opacity: file.isDownloadingCompleted ? 0.0 : 1.0 + Behavior on opacity { FadeAnimation {} } + anchors { + right: parent.right + bottom: parent.bottom + margins: Theme.paddingSmall + } + } + } + + Column { + id: labelsColumn + anchors { + left: leftButton.right + leftMargin: Theme.paddingSmall + right: copyButton.left + verticalCenter: leftButton.verticalCenter + } + + Label { + id: primaryLabel + width: parent.width + font.pixelSize: Theme.fontSizeSmall + fontSizeMode: Text.HorizontalFit + minimumPixelSize: Theme.fontSizeTiny + color: Theme.highlightColor + visible: text.length > 0 + truncationMode: TruncationMode.Fade + } + + Label { + id: secondaryLabel + width: parent.width + font.pixelSize: Theme.fontSizeExtraSmall + fontSizeMode: Text.HorizontalFit + minimumPixelSize: Theme.fontSizeTiny + color: Theme.secondaryHighlightColor + visible: text.length > 0 + truncationMode: TruncationMode.Fade + } + Item { + height: sizeLabel.height + width: parent.width + Label { + id: tertiaryLabel + font.pixelSize: Theme.fontSizeTiny + color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor + visible: text.length > 0 + truncationMode: TruncationMode.Fade + } + Label { + id: sizeLabel + anchors.right: parent.right + font.pixelSize: Theme.fontSizeTiny + color: tertiaryLabel.color + text: Format.formatFileSize(file.size || file.expectedSize) + visible: (file.size || file.expectedSize) > 0 + truncationMode: TruncationMode.Fade + } + } + } + IconButton { + id: copyButton + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } + opacity: file.isDownloadingCompleted ? 1.0 : 0.0 + width: file.isDownloadingCompleted ? Theme.itemSizeMedium : 0 + visible: opacity > 0 + + Behavior on opacity { FadeAnimation {} } + Behavior on width { NumberAnimation { duration: 200 } } + icon { + asynchronous: true + source: "../../../images/icon-m-copy-to-folder.svg" + sourceSize { + width: Theme.iconSizeMedium + height: Theme.iconSizeMedium + } + } + onClicked: { + tdLibWrapper.copyFileToDownloads(file.path); + // not persistent: + opacity = 0; + width = 0; + } + } + } +} diff --git a/qml/components/messageContent/MessageDocument.qml b/qml/components/messageContent/MessageDocument.qml index 12b6944..df16feb 100644 --- a/qml/components/messageContent/MessageDocument.qml +++ b/qml/components/messageContent/MessageDocument.qml @@ -18,108 +18,65 @@ */ import QtQuick 2.6 import Sailfish.Silica 1.0 +import "../../js/twemoji.js" as Emoji -MessageContentBase { +MessageContentFileInfoBase { + id: contentItem + fileInformation: rawMessage.content.document.document - id: documentPreviewItem - height: Theme.itemSizeLarge + primaryText: Emoji.emojify(rawMessage.content.document.file_name || "", primaryLabel.font.pixelSize) + secondaryText: Emoji.emojify(Functions.enhanceMessageText(rawMessage.content.caption) || "", secondaryLabel.font.pixelSize) - property var documentData: rawMessage.content.document - property bool openRequested: false; + minithumbnail: rawMessage.content.document.minithumbnail + thumbnail: rawMessage.content.document.thumbnail - Component.onCompleted: { - updateDocument(); - } - - function updateDocument() { - if (documentData) { - if (documentData.document.local.is_downloading_completed) { - downloadDocumentButton.visible = false; - openDocumentArea.visible = true; - } else { - openDocumentArea.visible = false; - downloadDocumentButton.visible = true; - } - } - } - - Connections { - target: tdLibWrapper - onFileUpdated: { - if (documentData) { - if (!fileInformation.remote.is_uploading_active && fileId === documentData.document.id && fileInformation.local.is_downloading_completed) { - downloadingProgressBar.visible = false; - documentData.document = fileInformation; - downloadDocumentButton.visible = false; - openDocumentArea.visible = true; - if (documentPreviewItem.openRequested) { - documentPreviewItem.openRequested = false; - tdLibWrapper.openFileOnDevice(documentData.document.local.path); - } - } - if (fileId === documentData.document.id) { - downloadingProgressBar.maximumValue = fileInformation.size; - downloadingProgressBar.value = fileInformation.local.downloaded_size; - } - } - } - } - - Button { - id: downloadDocumentButton - preferredWidth: Theme.buttonWidthMedium - anchors.centerIn: parent - text: qsTr("Download Document") - visible: false - highlighted: documentPreviewItem.highlighted || down + leftButton { + icon.source: Theme.iconForMimeType(rawMessage.content.document.mime_type) onClicked: { - downloadDocumentButton.visible = false; - downloadingProgressBar.visible = true; - tdLibWrapper.downloadFile(documentData.document.id); + if(file.isDownloadingCompleted) { + // in this case, the MouseArea should take over + tdLibWrapper.openFileOnDevice(file.path); + } else if(!file.isDownloadingActive) { + file.load(); + } else { + file.cancel() + } } } - ProgressBar { - id: downloadingProgressBar - minimumValue: 0 - maximumValue: 100 - value: 0 - visible: false - width: parent.width - anchors.centerIn: parent - } - - Column { - id: openDocumentArea - visible: false - spacing: Theme.paddingMedium - width: parent.width - - onVisibleChanged: { - visible ? (documentPreviewItem.height = openDocumentArea.height) : (documentPreviewItem.height = Theme.itemSizeLarge); - } - - Button { - id: openDocumentButton - preferredWidth: Theme.buttonWidthMedium - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Open Document") - highlighted: documentPreviewItem.highlighted || down - onClicked: { - documentPreviewItem.openRequested = true; - tdLibWrapper.openFileOnDevice(documentData.document.local.path); + states: [ + State { + when: file.isDownloadingCompleted + PropertyChanges { target: openMouseArea; enabled: true } + PropertyChanges { + target: primaryLabel + color: (contentItem.highlighted || openMouseArea.pressed) ? Theme.highlightColor : Theme.primaryColor + } + PropertyChanges { + target: secondaryLabel + color: (contentItem.highlighted || openMouseArea.pressed) ? Theme.secondaryHighlightColor : Theme.secondaryColor + } + PropertyChanges { + target: tertiaryLabel + color: (contentItem.highlighted || openMouseArea.pressed) ? Theme.secondaryHighlightColor : Theme.secondaryColor + } + PropertyChanges { + target: leftButton + highlighted: contentItem.highlighted || openMouseArea.pressed } } - Button { - id: copyDocumentButton - preferredWidth: Theme.buttonWidthMedium - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Copy Document to Downloads") - highlighted: documentPreviewItem.highlighted || down - onClicked: { - tdLibWrapper.copyFileToDownloads(documentData.document.local.path); - } + ] + MouseArea { + id: openMouseArea + enabled: file.isDownloadingCompleted + visible: enabled + anchors { + fill: primaryItem + rightMargin: copyButton.width + } + onClicked: { + tdLibWrapper.openFileOnDevice(file.path); } } } diff --git a/qml/components/messageContent/MessageLocation.qml b/qml/components/messageContent/MessageLocation.qml index c6c46d7..1b26e18 100644 --- a/qml/components/messageContent/MessageLocation.qml +++ b/qml/components/messageContent/MessageLocation.qml @@ -29,7 +29,7 @@ MessageContentBase { property string chatId: rawMessage.chat_id property var pictureFileInformation; - height: width / 2 + height: width * 0.66666666; property string fileExtra Component.onCompleted: { diff --git a/qml/components/messageContent/MessageVoiceNote.qml b/qml/components/messageContent/MessageVoiceNote.qml index 91b403e..5a387e0 100644 --- a/qml/components/messageContent/MessageVoiceNote.qml +++ b/qml/components/messageContent/MessageVoiceNote.qml @@ -18,4 +18,11 @@ */ import QtQuick 2.6 -MessageAudio {} +MessageAudio { + fileInformation: rawMessage.content.voice_note.voice + primaryText: qsTr("Voice Note") + secondaryText: "" + duration: rawMessage.content.voice_note.duration + thumbnail: null + minithumbnail: null +} diff --git a/qml/js/functions.js b/qml/js/functions.js index db2705f..b740273 100644 --- a/qml/js/functions.js +++ b/qml/js/functions.js @@ -79,7 +79,7 @@ function getMessageText(message, simple, currentUserId, ignoreEntities) { } case 'messageDocument': if (message.content.document.file_name !== "") { - return simple ? qsTr("Document: %1").arg(message.content.document.file_name) : (message.content.document.file_name + ( message.content.caption.text !== "" ? ("
" + enhanceMessageText(message.content.caption, ignoreEntities) ) : "")).trim(); + return simple ? qsTr("Document: %1").arg(message.content.document.file_name) : (message.content.caption.text !== "" ? enhanceMessageText(message.content.caption, ignoreEntities) : "").trim(); } else { return simple ? (myself ? qsTr("sent a document", "myself") : qsTr("sent a document")) : ""; } diff --git a/qml/pages/ChatPage.qml b/qml/pages/ChatPage.qml index 30ec601..0e59b1b 100644 --- a/qml/pages/ChatPage.qml +++ b/qml/pages/ChatPage.qml @@ -1062,9 +1062,8 @@ Page { return Functions.getVideoHeight(parentWidth, content.video); case "messageAudio": case "messageVoiceNote": - return Theme.itemSizeLarge; case "messageDocument": - return Theme.itemSizeSmall; + return Theme.itemSizeLarge; case "messageGame": return parentWidth * 0.66666666 + Theme.itemSizeLarge; // 2 / 3; case "messageLocation": diff --git a/src/tdlibfile.cpp b/src/tdlibfile.cpp index dece939..2326435 100644 --- a/src/tdlibfile.cpp +++ b/src/tdlibfile.cpp @@ -194,6 +194,16 @@ void TDLibFile::setAutoLoad(bool enableAutoLoad) } } +bool TDLibFile::cancel() +{ + if (id && tdLibWrapper && is_downloading_active) { + tdLibWrapper->cancelDownloadFile(id); + tdLibWrapper->deleteFile(id); + return true; + } + return false; +} + bool TDLibFile::load() { // Manual load ignores hold-off timer diff --git a/src/tdlibfile.h b/src/tdlibfile.h index 0616271..41d17a1 100644 --- a/src/tdlibfile.h +++ b/src/tdlibfile.h @@ -78,6 +78,7 @@ public: bool isUploadingActive() const; bool isUploadingCompleted() const; + Q_INVOKABLE bool cancel(); Q_INVOKABLE bool load(); signals: diff --git a/src/tdlibwrapper.cpp b/src/tdlibwrapper.cpp index f8cdcb8..5446f7e 100644 --- a/src/tdlibwrapper.cpp +++ b/src/tdlibwrapper.cpp @@ -1146,6 +1146,37 @@ void TDLibWrapper::sendBotStartMessage(qlonglong botUserId, qlonglong chatId, co this->sendRequest(requestObject); } +void TDLibWrapper::cancelDownloadFile(int fileId) +{ + LOG("Cancel Download File" << fileId); + QVariantMap requestObject; + requestObject.insert(_TYPE, "cancelDownloadFile"); + requestObject.insert("file_id", fileId); + requestObject.insert("only_if_pending", false); + + this->sendRequest(requestObject); +} + +void TDLibWrapper::cancelUploadFile(int fileId) +{ + LOG("Cancel Upload File" << fileId); + QVariantMap requestObject; + requestObject.insert(_TYPE, "cancelUploadFile"); + requestObject.insert("file_id", fileId); + + this->sendRequest(requestObject); +} + +void TDLibWrapper::deleteFile(int fileId) +{ + LOG("Delete cached File" << fileId); + QVariantMap requestObject; + requestObject.insert(_TYPE, "deleteFile"); + requestObject.insert("file_id", fileId); + + this->sendRequest(requestObject); +} + void TDLibWrapper::searchEmoji(const QString &queryString) { LOG("Searching emoji" << queryString); diff --git a/src/tdlibwrapper.h b/src/tdlibwrapper.h index 914ccf6..8b1ce90 100644 --- a/src/tdlibwrapper.h +++ b/src/tdlibwrapper.h @@ -193,6 +193,9 @@ public: Q_INVOKABLE void getInlineQueryResults(qlonglong botUserId, qlonglong chatId, const QVariantMap &userLocation, const QString &query, const QString &offset, const QString &extra); Q_INVOKABLE void sendInlineQueryResultMessage(qlonglong chatId, qlonglong threadId, qlonglong replyToMessageId, const QString &queryId, const QString &resultId); Q_INVOKABLE void sendBotStartMessage(qlonglong botUserId, qlonglong chatId, const QString ¶meter, const QString &extra); + Q_INVOKABLE void cancelDownloadFile(int fileId); + Q_INVOKABLE void cancelUploadFile(int fileId); + Q_INVOKABLE void deleteFile(int fileId); // Others (candidates for extraction ;)) Q_INVOKABLE void searchEmoji(const QString &queryString); diff --git a/translations/harbour-fernschreiber-de.ts b/translations/harbour-fernschreiber-de.ts index e3e2b09..28add57 100644 --- a/translations/harbour-fernschreiber-de.ts +++ b/translations/harbour-fernschreiber-de.ts @@ -930,21 +930,6 @@ Über Fernschreiber - - MessageDocument - - Download Document - Dokument herunterladen - - - Open Document - Dokument öffnen - - - Copy Document to Downloads - Dokument zu Downloads kopieren - - MessageListViewItem @@ -1075,6 +1060,13 @@ via %1 + + MessageVoiceNote + + Voice Note + + + NewChatPage diff --git a/translations/harbour-fernschreiber-en.ts b/translations/harbour-fernschreiber-en.ts index 5d697bd..3d0fcce 100644 --- a/translations/harbour-fernschreiber-en.ts +++ b/translations/harbour-fernschreiber-en.ts @@ -930,21 +930,6 @@ About Fernschreiber - - MessageDocument - - Download Document - Download Document - - - Open Document - Open Document - - - Copy Document to Downloads - Copy Document to Downloads - - MessageListViewItem @@ -1075,6 +1060,13 @@ via %1 + + MessageVoiceNote + + Voice Note + + + NewChatPage diff --git a/translations/harbour-fernschreiber-es.ts b/translations/harbour-fernschreiber-es.ts index 7cb5598..6bd0ef7 100644 --- a/translations/harbour-fernschreiber-es.ts +++ b/translations/harbour-fernschreiber-es.ts @@ -930,21 +930,6 @@ Acerca de - - MessageDocument - - Download Document - Bajar Documento - - - Open Document - Abrir Documento - - - Copy Document to Downloads - Copiar documento a Downloads - - MessageListViewItem @@ -1062,7 +1047,7 @@ %Ln vote(s) total number of total votes - + %Ln total de votos @@ -1075,6 +1060,13 @@ + + MessageVoiceNote + + Voice Note + + + NewChatPage diff --git a/translations/harbour-fernschreiber-fi.ts b/translations/harbour-fernschreiber-fi.ts index 89dd26a..f1595d0 100644 --- a/translations/harbour-fernschreiber-fi.ts +++ b/translations/harbour-fernschreiber-fi.ts @@ -931,21 +931,6 @@ Tietoa Fernschreiberista - - MessageDocument - - Download Document - Lataa dokumentti - - - Open Document - Avaa dokumentti - - - Copy Document to Downloads - - - MessageListViewItem @@ -1063,8 +1048,8 @@ %Ln vote(s) total number of total votes - - + yhteensä %Ln ääni + yhteensä %Ln ääntä @@ -1076,6 +1061,13 @@ + + MessageVoiceNote + + Voice Note + + + NewChatPage diff --git a/translations/harbour-fernschreiber-hu.ts b/translations/harbour-fernschreiber-hu.ts index 9e765a5..e289d2a 100644 --- a/translations/harbour-fernschreiber-hu.ts +++ b/translations/harbour-fernschreiber-hu.ts @@ -918,21 +918,6 @@ A Fernschreiber névjegye - - MessageDocument - - Download Document - Dokumentum letöltése - - - Open Document - Dokumentum megyitása - - - Copy Document to Downloads - - - MessageListViewItem @@ -1059,6 +1044,13 @@ + + MessageVoiceNote + + Voice Note + + + NewChatPage diff --git a/translations/harbour-fernschreiber-it.ts b/translations/harbour-fernschreiber-it.ts index 7fbe72d..3feba91 100644 --- a/translations/harbour-fernschreiber-it.ts +++ b/translations/harbour-fernschreiber-it.ts @@ -930,21 +930,6 @@ Informazioni su Fernschreiber - - MessageDocument - - Download Document - Scarica documento - - - Open Document - Apri documento - - - Copy Document to Downloads - - - MessageListViewItem @@ -1075,6 +1060,13 @@ + + MessageVoiceNote + + Voice Note + + + NewChatPage diff --git a/translations/harbour-fernschreiber-pl.ts b/translations/harbour-fernschreiber-pl.ts index 2164cd2..23e0666 100644 --- a/translations/harbour-fernschreiber-pl.ts +++ b/translations/harbour-fernschreiber-pl.ts @@ -942,21 +942,6 @@ O Fernschreiber - - MessageDocument - - Download Document - Pobierz dokument - - - Open Document - Otwórz dokument - - - Copy Document to Downloads - - - MessageListViewItem @@ -1091,6 +1076,13 @@ + + MessageVoiceNote + + Voice Note + + + NewChatPage diff --git a/translations/harbour-fernschreiber-ru.ts b/translations/harbour-fernschreiber-ru.ts index 2ec6396..23bfce7 100644 --- a/translations/harbour-fernschreiber-ru.ts +++ b/translations/harbour-fernschreiber-ru.ts @@ -942,21 +942,6 @@ О программе - - MessageDocument - - Download Document - Скачать документ - - - Open Document - Открыть документ - - - Copy Document to Downloads - Сохранить в Загрузках - - MessageListViewItem @@ -1091,6 +1076,13 @@ + + MessageVoiceNote + + Voice Note + + + NewChatPage diff --git a/translations/harbour-fernschreiber-sv.ts b/translations/harbour-fernschreiber-sv.ts index 3947051..48a27cd 100644 --- a/translations/harbour-fernschreiber-sv.ts +++ b/translations/harbour-fernschreiber-sv.ts @@ -930,21 +930,6 @@ Om Fernschreiber - - MessageDocument - - Download Document - Ladda ner dokument - - - Open Document - Öppna dokument - - - Copy Document to Downloads - - - MessageListViewItem @@ -1075,6 +1060,13 @@ + + MessageVoiceNote + + Voice Note + + + NewChatPage diff --git a/translations/harbour-fernschreiber-zh_CN.ts b/translations/harbour-fernschreiber-zh_CN.ts index 780e8cf..56823f6 100644 --- a/translations/harbour-fernschreiber-zh_CN.ts +++ b/translations/harbour-fernschreiber-zh_CN.ts @@ -918,21 +918,6 @@ 关于 Fernschreiber - - MessageDocument - - Download Document - 下载文档 - - - Open Document - 打开文档 - - - Copy Document to Downloads - 复制文档到下载 - - MessageListViewItem @@ -1047,7 +1032,7 @@ %Ln vote(s) total number of total votes - + 总计 %Ln 次投票 @@ -1059,6 +1044,13 @@ + + MessageVoiceNote + + Voice Note + + + NewChatPage diff --git a/translations/harbour-fernschreiber.ts b/translations/harbour-fernschreiber.ts index 2d67e32..bfdd7b4 100644 --- a/translations/harbour-fernschreiber.ts +++ b/translations/harbour-fernschreiber.ts @@ -930,21 +930,6 @@ About Fernschreiber - - MessageDocument - - Download Document - Download Document - - - Open Document - Open Document - - - Copy Document to Downloads - - - MessageListViewItem @@ -1075,6 +1060,13 @@ + + MessageVoiceNote + + Voice Note + + + NewChatPage