Merge pull request #327 from jgibbon/feature/messageContent_fileinfo_items

Add TDLibImage/TDLibThumbnail; rework Audio/VoiceNote/Document
This commit is contained in:
Sebastian Wolf 2021-01-18 19:21:23 +01:00 committed by GitHub
commit 32f884e547
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 666 additions and 697 deletions

View file

@ -61,6 +61,8 @@ DISTFILES += qml/harbour-fernschreiber.qml \
qml/components/ReplyMarkupButtons.qml \ qml/components/ReplyMarkupButtons.qml \
qml/components/StickerPicker.qml \ qml/components/StickerPicker.qml \
qml/components/PhotoTextsListItem.qml \ qml/components/PhotoTextsListItem.qml \
qml/components/TDLibImage.qml \
qml/components/TDLibThumbnail.qml \
qml/components/VoiceNoteOverlay.qml \ qml/components/VoiceNoteOverlay.qml \
qml/components/chatInformationPage/ChatInformationEditArea.qml \ qml/components/chatInformationPage/ChatInformationEditArea.qml \
qml/components/chatInformationPage/ChatInformationPageContent.qml \ qml/components/chatInformationPage/ChatInformationPageContent.qml \
@ -91,6 +93,7 @@ DISTFILES += qml/harbour-fernschreiber.qml \
qml/components/messageContent/MessageAnimation.qml \ qml/components/messageContent/MessageAnimation.qml \
qml/components/messageContent/MessageAudio.qml \ qml/components/messageContent/MessageAudio.qml \
qml/components/messageContent/MessageContentBase.qml \ qml/components/messageContent/MessageContentBase.qml \
qml/components/messageContent/MessageContentFileInfoBase.qml \
qml/components/messageContent/MessageDocument.qml \ qml/components/messageContent/MessageDocument.qml \
qml/components/messageContent/MessageGame.qml \ qml/components/messageContent/MessageGame.qml \
qml/components/messageContent/MessageLocation.qml \ qml/components/messageContent/MessageLocation.qml \

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="enable-background:new 0 0 64 64;"
viewBox="0 0 64 64"
y="0px"
x="0px"
id="Layer_1"
version="1.1"><metadata
id="metadata17"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs15">
</defs>
<path
style="fill:#ffffff"
d="m 37.855477,15.759299 c -1.73926,0.0029 -4.47725,1.553014 -6.64098,2.8829 -1.6826,1.034172 -3.75291,1.108852 -5.69886,1.124912 h -8.64063 c -1.034,0 -1.875,0.841 -1.875,1.875 v 8.070312 l 2,1.359376 v -9.304688 h 8.51563 c 2.75742,-0.06081 4.84074,-0.2051 6.5836,-1.513027 2.1377,-1.604239 4.80361,-2.501457 7.17616,-2.486973 h 18.51758 c 0.666,0 1.20703,0.541031 1.20703,1.207031 v 31.542969 c 0,0.689 -0.561,1.25 -1.25,1.25 h -39.5 c -0.689,0 -1.25,-0.561 -1.25,-1.25 V 42.08547 l -2,1.359375 v 7.072266 c 0,1.792 1.458,3.25 3.25,3.25 h 39.5 c 1.792,0 3.25,-1.458 3.25,-3.25 V 18.974142 c 0,-1.769 -1.43803,-3.207984 -3.20703,-3.208984 h -18.51758 c -0.4765,0 -0.94899,-0.01047 -1.41992,-0.0059 z"
id="path6-9" /><path
id="path11-6"
d="m 12.995027,29.476688 10.626,7.222 -10.626,7.222 v -14.444 m -2,-1.778 v 18 c 0,1.1 0.744,1.494 1.654,0.876 l 12.875,-8.752 c 0.455,-0.309 0.682,-0.717 0.682,-1.124 0,-0.408 -0.227,-0.815 -0.682,-1.124 l -12.875,-8.751 c -0.91,-0.619 -1.654,-0.225 -1.654,0.875 z"
style="fill:#ffffff" /><path
id="path5-0"
d="m 15.000437,21.683979 v -6.5 c 0,-1.832003 1.6,-3.500003 3.357,-3.500003 h 10.636 c 1.967,0 3.809,0.854 4.848,2.238003 l 3.457,2.756 c -2.99546,1.819905 -4.21018,2.916329 -6.43718,3.672261 -5.13259,1.742208 -10.47736,1.018549 -15.86082,1.333739 z"
style="opacity:0.6;fill:#ffffff" /><path
id="path9-3-6"
d="m 3.3670166,11.247132 c -0.004,1.278302 0.0179,17.502612 0.0179,17.502612 v 0.0059 0.0059 c 0.0626,4.141039 2.28159,6.407836 4.41211,7.382812 2.13052,0.974976 4.2070304,0.853516 4.2070304,0.853516"
style="color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" /></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 {} }
}
}
}
}

View file

@ -20,455 +20,77 @@ import QtQuick 2.6
import Sailfish.Silica 1.0 import Sailfish.Silica 1.0
import QtMultimedia 5.6 import QtMultimedia 5.6
import "../" import "../"
import "../../js/twemoji.js" as Emoji
import "../../js/functions.js" as Functions import "../../js/functions.js" as Functions
import "../../js/debug.js" as Debug import "../../js/debug.js" as Debug
MessageContentBase { MessageContentFileInfoBase {
id: audioMessageComponent id: contentItem
property var audioData: ( rawMessage.content['@type'] === "messageVoiceNote" ) ? rawMessage.content.voice_note : ( ( rawMessage.content['@type'] === "messageAudio" ) ? rawMessage.content.audio : ""); fileInformation: rawMessage.content.audio.audio
property string audioUrl; thumbnail: rawMessage.content.audio.album_cover_thumbnail
property int previewFileId; minithumbnail: rawMessage.content.audio.album_cover_minithumbnail
property int audioFileId;
property bool onScreen: messageListItem ? messageListItem.page.status === PageStatus.Active : true
property string audioType : "voiceNote";
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) { leftButton {
var minutes = Math.floor( rawSeconds / 60 ); icon.source: audioPlayer.playbackState === Audio.PlayingState || (file.isDownloadingActive && audioPlayer.autoPlay) ? "image://theme/icon-m-pause": "image://theme/icon-m-play"
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;
} 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;
}
}
}
}
Image {
id: placeholderImage
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
}
text: audioData.performer + (audioData.performer && audioData.title ? " - " : "") + audioData.title
font.pixelSize: Theme.fontSizeTiny
}
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: { onClicked: {
handlePlay(); if(!file.isDownloadingCompleted && !file.isDownloadingActive) {
} file.load();
} audioPlayer.autoPlay = true;
BusyIndicator { } else if(file.isDownloadingActive) {
id: audioDownloadBusyIndicator audioPlayer.autoPlay = false;
running: false file.cancel();
visible: running } else if(file.isDownloadingCompleted) {
anchors.centerIn: parent //playPause
size: BusyIndicatorSize.Large if(audioPlayer.playbackState === Audio.PlayingState) {
} audioPlayer.pause();
}
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
}
}
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 { } else {
messageAudio.play(); audioPlayer.play();
} }
} }
} }
}
property int duration: rawMessage.content.audio.duration
Audio { Audio {
id: messageAudio id: audioPlayer
source: file.isDownloadingCompleted ? file.path : ""
Component.onCompleted: { autoPlay: false
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 { Slider {
id: messageAudioSlider
width: parent.width 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 { anchors {
bottom: parent.bottom left: parent.left
bottomMargin: Theme.paddingSmall leftMargin: -Screen.width/16
horizontalCenter: positionTextOverlay.horizontalCenter right: parent.right
} rightMargin: -Screen.width/16
wrapMode: Text.Wrap top: primaryItem.bottom
text: ( messageAudio.duration - messageAudio.position ) > 0 ? getTimeString(Math.round((messageAudio.duration - messageAudio.position) / 1000)) : "-:-" topMargin: -height/3
}
} }
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 } }
highlighted: contentItem.highlighted || down
onReleased: {
audioPlayer.seek(Math.floor(value));
audioPlayer.play();
} }
} }
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}
}
}

View file

@ -18,108 +18,65 @@
*/ */
import QtQuick 2.6 import QtQuick 2.6
import Sailfish.Silica 1.0 import Sailfish.Silica 1.0
import "../../js/twemoji.js" as Emoji
MessageContentBase { MessageContentFileInfoBase {
id: contentItem
fileInformation: rawMessage.content.document.document
id: documentPreviewItem primaryText: Emoji.emojify(rawMessage.content.document.file_name || "", primaryLabel.font.pixelSize)
height: Theme.itemSizeLarge secondaryText: Emoji.emojify(Functions.enhanceMessageText(rawMessage.content.caption) || "", secondaryLabel.font.pixelSize)
property var documentData: rawMessage.content.document minithumbnail: rawMessage.content.document.minithumbnail
property bool openRequested: false; thumbnail: rawMessage.content.document.thumbnail
Component.onCompleted: { leftButton {
updateDocument(); icon.source: Theme.iconForMimeType(rawMessage.content.document.mime_type)
} onClicked: {
if(file.isDownloadingCompleted) {
function updateDocument() { // in this case, the MouseArea should take over
if (documentData) { tdLibWrapper.openFileOnDevice(file.path);
if (documentData.document.local.is_downloading_completed) { } else if(!file.isDownloadingActive) {
downloadDocumentButton.visible = false; file.load();
openDocumentArea.visible = true;
} else { } else {
openDocumentArea.visible = false; file.cancel()
downloadDocumentButton.visible = true;
} }
} }
} }
Connections { states: [
target: tdLibWrapper State {
onFileUpdated: { when: file.isDownloadingCompleted
if (documentData) { PropertyChanges { target: openMouseArea; enabled: true }
if (!fileInformation.remote.is_uploading_active && fileId === documentData.document.id && fileInformation.local.is_downloading_completed) { PropertyChanges {
downloadingProgressBar.visible = false; target: primaryLabel
documentData.document = fileInformation; color: (contentItem.highlighted || openMouseArea.pressed) ? Theme.highlightColor : Theme.primaryColor
downloadDocumentButton.visible = false;
openDocumentArea.visible = true;
if (documentPreviewItem.openRequested) {
documentPreviewItem.openRequested = false;
tdLibWrapper.openFileOnDevice(documentData.document.local.path);
} }
PropertyChanges {
target: secondaryLabel
color: (contentItem.highlighted || openMouseArea.pressed) ? Theme.secondaryHighlightColor : Theme.secondaryColor
} }
if (fileId === documentData.document.id) { PropertyChanges {
downloadingProgressBar.maximumValue = fileInformation.size; target: tertiaryLabel
downloadingProgressBar.value = fileInformation.local.downloaded_size; color: (contentItem.highlighted || openMouseArea.pressed) ? Theme.secondaryHighlightColor : Theme.secondaryColor
}
} }
PropertyChanges {
target: leftButton
highlighted: contentItem.highlighted || openMouseArea.pressed
} }
} }
Button { ]
id: downloadDocumentButton MouseArea {
preferredWidth: Theme.buttonWidthMedium id: openMouseArea
anchors.centerIn: parent enabled: file.isDownloadingCompleted
text: qsTr("Download Document") visible: enabled
visible: false anchors {
highlighted: documentPreviewItem.highlighted || down fill: primaryItem
rightMargin: copyButton.width
}
onClicked: { onClicked: {
downloadDocumentButton.visible = false; tdLibWrapper.openFileOnDevice(file.path);
downloadingProgressBar.visible = true;
tdLibWrapper.downloadFile(documentData.document.id);
}
}
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);
}
}
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);
}
} }
} }
} }

View file

@ -29,7 +29,7 @@ MessageContentBase {
property string chatId: rawMessage.chat_id property string chatId: rawMessage.chat_id
property var pictureFileInformation; property var pictureFileInformation;
height: width / 2 height: width * 0.66666666;
property string fileExtra property string fileExtra
Component.onCompleted: { Component.onCompleted: {

View file

@ -18,4 +18,11 @@
*/ */
import QtQuick 2.6 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
}

View file

@ -79,7 +79,7 @@ function getMessageText(message, simple, currentUserId, ignoreEntities) {
} }
case 'messageDocument': case 'messageDocument':
if (message.content.document.file_name !== "") { 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 !== "" ? ("<br />" + 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 { } else {
return simple ? (myself ? qsTr("sent a document", "myself") : qsTr("sent a document")) : ""; return simple ? (myself ? qsTr("sent a document", "myself") : qsTr("sent a document")) : "";
} }

View file

@ -1062,9 +1062,8 @@ Page {
return Functions.getVideoHeight(parentWidth, content.video); return Functions.getVideoHeight(parentWidth, content.video);
case "messageAudio": case "messageAudio":
case "messageVoiceNote": case "messageVoiceNote":
return Theme.itemSizeLarge;
case "messageDocument": case "messageDocument":
return Theme.itemSizeSmall; return Theme.itemSizeLarge;
case "messageGame": case "messageGame":
return parentWidth * 0.66666666 + Theme.itemSizeLarge; // 2 / 3; return parentWidth * 0.66666666 + Theme.itemSizeLarge; // 2 / 3;
case "messageLocation": case "messageLocation":

View file

@ -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() bool TDLibFile::load()
{ {
// Manual load ignores hold-off timer // Manual load ignores hold-off timer

View file

@ -78,6 +78,7 @@ public:
bool isUploadingActive() const; bool isUploadingActive() const;
bool isUploadingCompleted() const; bool isUploadingCompleted() const;
Q_INVOKABLE bool cancel();
Q_INVOKABLE bool load(); Q_INVOKABLE bool load();
signals: signals:

View file

@ -1146,6 +1146,37 @@ void TDLibWrapper::sendBotStartMessage(qlonglong botUserId, qlonglong chatId, co
this->sendRequest(requestObject); 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) void TDLibWrapper::searchEmoji(const QString &queryString)
{ {
LOG("Searching emoji" << queryString); LOG("Searching emoji" << queryString);

View file

@ -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 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 sendInlineQueryResultMessage(qlonglong chatId, qlonglong threadId, qlonglong replyToMessageId, const QString &queryId, const QString &resultId);
Q_INVOKABLE void sendBotStartMessage(qlonglong botUserId, qlonglong chatId, const QString &parameter, const QString &extra); Q_INVOKABLE void sendBotStartMessage(qlonglong botUserId, qlonglong chatId, const QString &parameter, 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 ;)) // Others (candidates for extraction ;))
Q_INVOKABLE void searchEmoji(const QString &queryString); Q_INVOKABLE void searchEmoji(const QString &queryString);

View file

@ -930,21 +930,6 @@
<translation>Über Fernschreiber</translation> <translation>Über Fernschreiber</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation>Dokument herunterladen</translation>
</message>
<message>
<source>Open Document</source>
<translation>Dokument öffnen</translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation>Dokument zu Downloads kopieren</translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1075,6 +1060,13 @@
<translation>via %1</translation> <translation>via %1</translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>

View file

@ -930,21 +930,6 @@
<translation>About Fernschreiber</translation> <translation>About Fernschreiber</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation>Download Document</translation>
</message>
<message>
<source>Open Document</source>
<translation>Open Document</translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation>Copy Document to Downloads</translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1075,6 +1060,13 @@
<translation>via %1</translation> <translation>via %1</translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>

View file

@ -930,21 +930,6 @@
<translation>Acerca de</translation> <translation>Acerca de</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation>Bajar Documento</translation>
</message>
<message>
<source>Open Document</source>
<translation>Abrir Documento</translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation>Copiar documento a Downloads</translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1062,7 +1047,7 @@
<source>%Ln vote(s) total</source> <source>%Ln vote(s) total</source>
<comment>number of total votes</comment> <comment>number of total votes</comment>
<translation type="unfinished"> <translation type="unfinished">
<numerusform></numerusform> <numerusform>%Ln total de votos</numerusform>
<numerusform></numerusform> <numerusform></numerusform>
</translation> </translation>
</message> </message>
@ -1075,6 +1060,13 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>

View file

@ -931,21 +931,6 @@
<translation>Tietoa Fernschreiberista</translation> <translation>Tietoa Fernschreiberista</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation>Lataa dokumentti</translation>
</message>
<message>
<source>Open Document</source>
<translation>Avaa dokumentti</translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1063,8 +1048,8 @@
<source>%Ln vote(s) total</source> <source>%Ln vote(s) total</source>
<comment>number of total votes</comment> <comment>number of total votes</comment>
<translation type="unfinished"> <translation type="unfinished">
<numerusform></numerusform> <numerusform>yhteensä %Ln ääni </numerusform>
<numerusform></numerusform> <numerusform>yhteensä %Ln ääntä</numerusform>
</translation> </translation>
</message> </message>
</context> </context>
@ -1076,6 +1061,13 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>

View file

@ -918,21 +918,6 @@
<translation>A Fernschreiber névjegye</translation> <translation>A Fernschreiber névjegye</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation type="unfinished">Dokumentum letöltése</translation>
</message>
<message>
<source>Open Document</source>
<translation type="unfinished">Dokumentum megyitása</translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1059,6 +1044,13 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>

View file

@ -930,21 +930,6 @@
<translation>Informazioni su Fernschreiber</translation> <translation>Informazioni su Fernschreiber</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation type="unfinished">Scarica documento</translation>
</message>
<message>
<source>Open Document</source>
<translation type="unfinished">Apri documento</translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1075,6 +1060,13 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>

View file

@ -942,21 +942,6 @@
<translation>O Fernschreiber</translation> <translation>O Fernschreiber</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation type="unfinished">Pobierz dokument</translation>
</message>
<message>
<source>Open Document</source>
<translation type="unfinished">Otwórz dokument</translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1091,6 +1076,13 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>

View file

@ -942,21 +942,6 @@
<translation>О программе</translation> <translation>О программе</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation type="unfinished">Скачать документ</translation>
</message>
<message>
<source>Open Document</source>
<translation type="unfinished">Открыть документ</translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation type="unfinished">Сохранить в Загрузках</translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1091,6 +1076,13 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>

View file

@ -930,21 +930,6 @@
<translation>Om Fernschreiber</translation> <translation>Om Fernschreiber</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation type="unfinished">Ladda ner dokument</translation>
</message>
<message>
<source>Open Document</source>
<translation type="unfinished">Öppna dokument</translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1075,6 +1060,13 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>

View file

@ -918,21 +918,6 @@
<translation> Fernschreiber</translation> <translation> Fernschreiber</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Open Document</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1047,7 +1032,7 @@
<source>%Ln vote(s) total</source> <source>%Ln vote(s) total</source>
<comment>number of total votes</comment> <comment>number of total votes</comment>
<translation type="unfinished"> <translation type="unfinished">
<numerusform></numerusform> <numerusform> %Ln </numerusform>
</translation> </translation>
</message> </message>
</context> </context>
@ -1059,6 +1044,13 @@
<translation> %1</translation> <translation> %1</translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>

View file

@ -930,21 +930,6 @@
<translation>About Fernschreiber</translation> <translation>About Fernschreiber</translation>
</message> </message>
</context> </context>
<context>
<name>MessageDocument</name>
<message>
<source>Download Document</source>
<translation type="unfinished">Download Document</translation>
</message>
<message>
<source>Open Document</source>
<translation type="unfinished">Open Document</translation>
</message>
<message>
<source>Copy Document to Downloads</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>MessageListViewItem</name> <name>MessageListViewItem</name>
<message> <message>
@ -1075,6 +1060,13 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>MessageVoiceNote</name>
<message>
<source>Voice Note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>NewChatPage</name> <name>NewChatPage</name>
<message> <message>