diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro index 3b5d9ca..55086b8 100644 --- a/harbour-fernschreiber.pro +++ b/harbour-fernschreiber.pro @@ -21,6 +21,7 @@ SOURCES += src/harbour-fernschreiber.cpp \ src/tdlibwrapper.cpp DISTFILES += qml/harbour-fernschreiber.qml \ + qml/components/ImagePreview.qml \ qml/js/functions.js \ qml/pages/ChatPage.qml \ qml/pages/CoverPage.qml \ diff --git a/qml/components/AppNotification.qml b/qml/components/AppNotification.qml new file mode 100644 index 0000000..eca2c97 --- /dev/null +++ b/qml/components/AppNotification.qml @@ -0,0 +1,75 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf + + 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.0 +import QtGraphicalEffects 1.0 +import Sailfish.Silica 1.0 + +Item { + id: notificationItem + z: 42 + y: -100 + Behavior on y { PropertyAnimation { easing.type: Easing.OutCubic } } + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width - 2 * Theme.horizontalPageMargin + + function show(message, additionalInformation) { + notificationTextItem.text = message; + if (additionalInformation) { + notificationTextItem.additionalInformation = additionalInformation; + } + notificationTimer.start(); + notificationTextItem.visible = true; + notificationTextItem.opacity = 1; + notificationItem.y = notificationItem.parent.height / 2; + } + + Connections { + target: notificationTimer + onTriggered: { + notificationItem.y = notificationItem.parent.height + 100; + notificationTextItem.opacity = 0; + notificationResetter.start(); + } + } + + Connections { + target: notificationResetter + onTriggered: { + notificationTextItem.visible = false; + notificationItem.y = -100 + } + } + + Timer { + id: notificationTimer + repeat: false + interval: 3500 + } + + Timer { + id: notificationResetter + repeat: false + interval: 1000 + } + + AppNotificationItem { + id: notificationTextItem + } + +} diff --git a/qml/components/AppNotificationItem.qml b/qml/components/AppNotificationItem.qml new file mode 100644 index 0000000..92a518f --- /dev/null +++ b/qml/components/AppNotificationItem.qml @@ -0,0 +1,73 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf + + 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.0 +import QtGraphicalEffects 1.0 +import Sailfish.Silica 1.0 + +Item { + + id: notificationItem + + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width + height: notificationText.height + 2 * Theme.paddingMedium + visible: false + opacity: 0 + Behavior on opacity { NumberAnimation {} } + + property string text; + property string additionalInformation; + + Rectangle { + id: notificationRectangleBackground + anchors.fill: parent + color: "black" + opacity: 0.6 + radius: parent.width / 15 + } + Rectangle { + id: notificationRectangle + anchors.fill: parent + color: Theme.highlightColor + opacity: 0.6 + radius: parent.width / 15 + } + + Text { + id: notificationText + color: Theme.primaryColor + text: notificationItem.text + font.pixelSize: Theme.fontSizeSmall + font.bold: true + width: parent.width - 2 * Theme.paddingMedium + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + } + + MouseArea { + anchors.fill: parent + onClicked: { + tdLibWrapper.handleAdditionalInformation(notificationItem.additionalInformation); + } + visible: additionalInformation ? true : false + } +} diff --git a/qml/components/BackgroundProgressIndicator.qml b/qml/components/BackgroundProgressIndicator.qml new file mode 100644 index 0000000..a6c8dbe --- /dev/null +++ b/qml/components/BackgroundProgressIndicator.qml @@ -0,0 +1,58 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf + + 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.0 +import Sailfish.Silica 1.0 + +Item { + + id: backgroundProgressIndicator + + property bool withPercentage : false; + property bool small : false; + property int progress; + + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + width: small ? parent.width / 2 : parent.width + height: small ? parent.height / 2 : parent.height + + Behavior on opacity { NumberAnimation {} } + visible: progress < 100 + opacity: progress < 100 ? 1 : 0 + ProgressCircle { + id: imageProgressCircle + width: withPercentage ? parent.height / 2 : parent.height + height: withPercentage ? parent.height / 2 : parent.height + value: backgroundProgressIndicator.progress / 100 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + } + Text { + id: imageProgressText + font.pixelSize: Theme.fontSizeExtraSmall + color: Theme.secondaryColor + text: withPercentage ? qsTr("%1 \%").arg(backgroundProgressIndicator.progress) : qsTr("%1").arg(backgroundProgressIndicator.progress) + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + visible: backgroundProgressIndicator.progress < 100 ? true : false + } + +} diff --git a/qml/components/ImagePreview.qml b/qml/components/ImagePreview.qml new file mode 100644 index 0000000..1a7d4ea --- /dev/null +++ b/qml/components/ImagePreview.qml @@ -0,0 +1,99 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf + + 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.5 +import QtGraphicalEffects 1.0 +import Sailfish.Silica 1.0 + +Item { + + id: imagePreviewItem + + property variant photoData; + property variant pictureFileInformation; + + Component.onCompleted: { + updatePicture(); + } + + function updatePicture() { + if (typeof photoData === "object") { + // Check first which size fits best... + for (var i = 0; i < photoData.sizes.length; i++) { + imagePreviewItem.pictureFileInformation = photoData.sizes[i].photo; + if (photoData.sizes[i].width >= imagePreviewItem.width) { + break; + } + } + + if (imagePreviewItem.pictureFileInformation.local.is_downloading_completed) { + singleImage.source = imagePreviewItem.pictureFileInformation.local.path; + } else { + tdLibWrapper.downloadFile(imagePreviewItem.pictureFileInformation.id); + } + } + } + + Connections { + target: tdLibWrapper + onFileUpdated: { + if (typeof imagePreviewItem.pictureFileInformation !== "undefined" && fileId === imagePreviewItem.pictureFileInformation.id) { + console.log("File updated, completed? " + fileInformation.local.is_downloading_completed); + if (fileInformation.local.is_downloading_completed) { + imagePreviewItem.pictureFileInformation = fileInformation; + singleImage.source = fileInformation.local.path; + } + } + } + } + + Image { + id: singleImage + width: parent.width - Theme.paddingSmall + height: parent.height - Theme.paddingSmall + anchors.centerIn: parent + + fillMode: Image.PreserveAspectCrop + autoTransform: true + asynchronous: true + visible: status === Image.Ready + opacity: status === Image.Ready ? 1 : 0 + Behavior on opacity { NumberAnimation {} } + MouseArea { + anchors.fill: parent + onClicked: { + pageStack.push(Qt.resolvedUrl("../pages/ImagePage.qml"), { "photoData" : imagePreviewItem.photoData, "pictureFileInformation" : imagePreviewItem.pictureFileInformation }); + } + } + } + + Image { + id: imageLoadingBackgroundImage + source: "../../images/background" + ( Theme.colorScheme ? "-black" : "-white" ) + ".png" + anchors { + centerIn: parent + } + width: parent.width - Theme.paddingMedium + height: parent.height - Theme.paddingMedium + visible: singleImage.status !== Image.Ready + + fillMode: Image.PreserveAspectFit + opacity: 0.15 + } + +} diff --git a/qml/js/functions.js b/qml/js/functions.js index 9db3b3e..df8388e 100644 --- a/qml/js/functions.js +++ b/qml/js/functions.js @@ -36,30 +36,33 @@ function getMessageText(message, simple) { } if (message.content['@type'] === 'messagePhoto') { if (message.content.caption.text !== "") { - return qsTr("Picture: %1").arg(simple ? message.content.caption.text : enhanceMessageText(message.content.caption)) + return simple ? qsTr("Picture: %1").arg(message.content.caption.text) : enhanceMessageText(message.content.caption) } else { - return qsTr("shared a picture"); + return simple ? qsTr("shared a picture") : ""; } } if (message.content['@type'] === 'messageVideo') { if (message.content.caption.text !== "") { - return qsTr("Video: %1").arg(simple ? message.content.caption.text : enhanceMessageText(message.content.caption)) + return simple ? qsTr("Video: %1").arg(message.content.caption.text) : enhanceMessageText(message.content.caption) } else { - return qsTr("shared a video"); + //ENABLE when ready: return simple ? qsTr("shared a video") : ""; + qsTr("shared a video"); } } if (message.content['@type'] === 'messageAudio') { if (message.content.caption.text !== "") { - return qsTr("Audio: %1").arg(simple ? message.content.caption.text : enhanceMessageText(message.content.caption)) + return simple ? qsTr("Audio: %1").arg(message.content.caption.text) : enhanceMessageText(message.content.caption) } else { - return qsTr("shared an audio"); + //ENABLE when ready: return simple ? qsTr("shared an audio") : ""; + qsTr("shared an audio"); } } if (message.content['@type'] === 'messageVoiceNote') { if (message.content.caption.text !== "") { - return qsTr("Voice Note: %1").arg(simple ? message.content.caption.text : enhanceMessageText(message.content.caption)) + return simple ? qsTr("Voice Note: %1").arg(message.content.caption.text) : enhanceMessageText(message.content.caption) } else { - return qsTr("shared a voice note"); + //ENABLE when ready: return simple ? qsTr("shared a voice note") : ""; + qsTr("shared a voice note"); } } if (message.content['@type'] === 'messageLocation') { diff --git a/qml/pages/ChatPage.qml b/qml/pages/ChatPage.qml index cb02106..ec040b7 100644 --- a/qml/pages/ChatPage.qml +++ b/qml/pages/ChatPage.qml @@ -84,7 +84,6 @@ Page { } function initializePage() { - tdLibWrapper.openChat(chatInformation.id); chatModel.initialize(chatInformation.id); var chatType = chatInformation.type['@type']; isPrivateChat = ( chatType === "chatTypePrivate" ); @@ -112,6 +111,9 @@ Page { } onStatusChanged: { + if (status === PageStatus.Activating) { + tdLibWrapper.openChat(chatInformation.id); + } if (status === PageStatus.Deactivating) { tdLibWrapper.closeChat(chatInformation.id); } @@ -342,6 +344,14 @@ Page { } horizontalAlignment: (chatPage.myUserId === display.sender_user_id) ? Text.AlignRight : Text.AlignLeft linkColor: Theme.highlightColor + visible: (text !== "") + } + + ImagePreview { + photoData: ( display.content['@type'] === "messagePhoto" ) ? display.content.photo : "" + width: parent.width + height: parent.width * 2 / 3 + visible: display.content['@type'] === "messagePhoto" } Timer { diff --git a/qml/pages/ImagePage.qml b/qml/pages/ImagePage.qml new file mode 100644 index 0000000..513eb0c --- /dev/null +++ b/qml/pages/ImagePage.qml @@ -0,0 +1,186 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf + + 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.0 +import Sailfish.Silica 1.0 +import QtMultimedia 5.0 +import "../components" +import "../js/functions.js" as Functions + +Page { + id: imagePage + allowedOrientations: Orientation.All + + property variant photoData; + property variant pictureFileInformation; + + property string imageUrl; + property int imageWidth; + property int imageHeight; + + property real imageSizeFactor : imageWidth / imageHeight; + property real screenSizeFactor: imagePage.width / imagePage.height; + property real sizingFactor : imageSizeFactor >= screenSizeFactor ? imagePage.width / imageWidth : imagePage.height / imageHeight; + + property real previousScale : 1; + property real centerX; + property real centerY; + property real oldCenterX; + property real oldCenterY; + + Component.onCompleted: { + updatePicture(); + } + + function updatePicture() { + if (typeof photoData === "object") { + // Check first which size fits best... + for (var i = 0; i < photoData.sizes.length; i++) { + imagePage.imageWidth = photoData.sizes[i].width; + imagePage.imageHeight = photoData.sizes[i].height; + imagePage.pictureFileInformation = photoData.sizes[i].photo; + if (photoData.sizes[i].width >= imagePage.width) { + break; + } + } + + if (imagePage.pictureFileInformation.local.is_downloading_completed) { + imagePage.imageUrl = imagePage.pictureFileInformation.local.path; + } else { + tdLibWrapper.downloadFile(imagePage.pictureFileInformation.id); + } + } + } + + Connections { + target: tdLibWrapper + onFileUpdated: { + if (fileId === imagePage.pictureFileInformation.id) { + console.log("File updated, completed? " + fileInformation.local.is_downloading_completed); + if (fileInformation.local.is_downloading_completed) { + imagePage.pictureFileInformation = fileInformation; + imagePage.imageUrl = fileInformation.local.path; + } + } + } + onCopyToDownloadsSuccessful: { + imageNotification.show(qsTr("Download of %1 successful.").arg(fileName), filePath); + } + + onCopyToDownloadsError: { + imageNotification.show(qsTr("Download failed.")); + } + } + + SilicaFlickable { + id: imageFlickable + anchors.fill: parent + clip: true + contentWidth: imagePinchArea.width; + contentHeight: imagePinchArea.height; + + PullDownMenu { + visible: (typeof imagePage.imageUrl !== "undefined") + MenuItem { + text: qsTr("Download Picture") + onClicked: { + tdLibWrapper.copyPictureToDownloads(imagePage.imageUrl); + } + } + } + + transitions: Transition { + NumberAnimation { properties: "contentX, contentY"; easing.type: Easing.Linear } + } + + AppNotification { + id: imageNotification + } + + PinchArea { + id: imagePinchArea + width: Math.max( singleImage.width * singleImage.scale, imageFlickable.width ) + height: Math.max( singleImage.height * singleImage.scale, imageFlickable.height ) + + enabled: singleImage.visible + pinch { + target: singleImage + minimumScale: 1 + maximumScale: 4 + } + + onPinchUpdated: { + imagePage.centerX = pinch.center.x; + imagePage.centerY = pinch.center.y; + } + + Image { + id: singleImage + source: imageUrl + width: imagePage.imageWidth * imagePage.sizingFactor + height: imagePage.imageHeight * imagePage.sizingFactor + anchors.centerIn: parent + + fillMode: Image.PreserveAspectFit + + visible: status === Image.Ready ? true : false + opacity: status === Image.Ready ? 1 : 0 + Behavior on opacity { NumberAnimation {} } + onScaleChanged: { + var newWidth = singleImage.width * singleImage.scale; + var newHeight = singleImage.height * singleImage.scale; + var oldWidth = singleImage.width * imagePage.previousScale; + var oldHeight = singleImage.height * imagePage.previousScale; + var widthDifference = newWidth - oldWidth; + var heightDifference = newHeight - oldHeight; + + if (oldWidth > imageFlickable.width || newWidth > imageFlickable.width) { + var xRatioNew = imagePage.centerX / newWidth; + var xRatioOld = imagePage.centerX / oldHeight; + imageFlickable.contentX = imageFlickable.contentX + ( xRatioNew * widthDifference ); + } + if (oldHeight > imageFlickable.height || newHeight > imageFlickable.height) { + var yRatioNew = imagePage.centerY / newHeight; + var yRatioOld = imagePage.centerY / oldHeight; + imageFlickable.contentY = imageFlickable.contentY + ( yRatioNew * heightDifference ); + } + + imagePage.previousScale = singleImage.scale; + imagePage.oldCenterX = imagePage.centerX; + imagePage.oldCenterY = imagePage.centerY; + } + } + } + + } + + Image { + id: imageLoadingBackgroundImage + source: "../../images/background" + ( Theme.colorScheme ? "-black" : "-white" ) + ".png" + anchors { + centerIn: parent + } + width: parent.width - Theme.paddingMedium + height: parent.height - Theme.paddingMedium + visible: singleImage.status !== Image.Ready + + fillMode: Image.PreserveAspectFit + opacity: 0.15 + } + +} diff --git a/src/tdlibreceiver.cpp b/src/tdlibreceiver.cpp index 8b9d538..88f2890 100644 --- a/src/tdlibreceiver.cpp +++ b/src/tdlibreceiver.cpp @@ -60,6 +60,7 @@ void TDLibReceiver::processReceivedDocument(const QJsonDocument &receivedJsonDoc if (objectTypeName == "updateUser") { this->processUpdateUser(receivedInformation); } if (objectTypeName == "updateUserStatus") { this->processUpdateUserStatus(receivedInformation); } if (objectTypeName == "updateFile") { this->processUpdateFile(receivedInformation); } + if (objectTypeName == "file") { this->processFile(receivedInformation); } if (objectTypeName == "updateNewChat") { this->processUpdateNewChat(receivedInformation); } if (objectTypeName == "updateUnreadMessageCount") { this->processUpdateUnreadMessageCount(receivedInformation); } if (objectTypeName == "updateUnreadChatCount") { this->processUpdateUnreadChatCount(receivedInformation); } @@ -123,6 +124,12 @@ void TDLibReceiver::processUpdateFile(const QVariantMap &receivedInformation) emit fileUpdated(fileInformation); } +void TDLibReceiver::processFile(const QVariantMap &receivedInformation) +{ + qDebug() << "[TDLibReceiver] File was updated: " << receivedInformation.value("id").toString(); + emit fileUpdated(receivedInformation); +} + void TDLibReceiver::processUpdateNewChat(const QVariantMap &receivedInformation) { QVariantMap chatInformation = receivedInformation.value("chat").toMap(); diff --git a/src/tdlibreceiver.h b/src/tdlibreceiver.h index 234d8d3..11905a1 100644 --- a/src/tdlibreceiver.h +++ b/src/tdlibreceiver.h @@ -67,6 +67,7 @@ private: void processUpdateUser(const QVariantMap &receivedInformation); void processUpdateUserStatus(const QVariantMap &receivedInformation); void processUpdateFile(const QVariantMap &receivedInformation); + void processFile(const QVariantMap &receivedInformation); void processUpdateNewChat(const QVariantMap &receivedInformation); void processUpdateUnreadMessageCount(const QVariantMap &receivedInformation); void processUpdateUnreadChatCount(const QVariantMap &receivedInformation); diff --git a/src/tdlibwrapper.cpp b/src/tdlibwrapper.cpp index e252da1..38fabcb 100644 --- a/src/tdlibwrapper.cpp +++ b/src/tdlibwrapper.cpp @@ -20,9 +20,13 @@ #include "tdlibwrapper.h" #include "tdlibsecrets.h" #include +#include +#include #include +#include #include #include +#include TDLibWrapper::TDLibWrapper(QObject *parent) : QObject(parent) { @@ -229,6 +233,36 @@ QVariantMap TDLibWrapper::getSuperGroup(const QString &groupId) return this->superGroups.value(groupId).toMap(); } +void TDLibWrapper::copyPictureToDownloads(const QString &filePath) +{ + qDebug() << "[TDLibWrapper] Copy picture to downloads " << filePath; + QFileInfo fileInfo(filePath); + if (fileInfo.exists()) { + QString downloadFilePath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + fileInfo.fileName(); + if (QFile::copy(filePath, downloadFilePath)) { + emit copyToDownloadsSuccessful(fileInfo.fileName(), downloadFilePath); + } else { + emit copyToDownloadsError(fileInfo.fileName(), downloadFilePath); + } + } else { + emit copyToDownloadsError(fileInfo.fileName(), filePath); + } +} + +void TDLibWrapper::handleAdditionalInformation(const QString &additionalInformation) +{ + qDebug() << "[TDLibWrapper] Additional information: " << additionalInformation; + // For now only used to open downloaded files... + QStringList argumentsList; + argumentsList.append(additionalInformation); + bool successfullyStarted = QProcess::startDetached("xdg-open", argumentsList); + if (successfullyStarted) { + qDebug() << "Successfully opened file " << additionalInformation; + } else { + qDebug() << "Error opening file " << additionalInformation; + } +} + void TDLibWrapper::handleVersionDetected(const QString &version) { this->version = version; diff --git a/src/tdlibwrapper.h b/src/tdlibwrapper.h index cbbda40..3b3fc00 100644 --- a/src/tdlibwrapper.h +++ b/src/tdlibwrapper.h @@ -67,6 +67,8 @@ public: Q_INVOKABLE QVariantMap getUnreadChatInformation(); Q_INVOKABLE QVariantMap getBasicGroup(const QString &groupId); Q_INVOKABLE QVariantMap getSuperGroup(const QString &groupId); + Q_INVOKABLE void copyPictureToDownloads(const QString &filePath); + Q_INVOKABLE void handleAdditionalInformation(const QString &additionalInformation); // Direct TDLib functions Q_INVOKABLE void sendRequest(const QVariantMap &requestObject); @@ -99,6 +101,8 @@ signals: void chatOnlineMemberCountUpdated(const QString &chatId, const int &onlineMemberCount); void messagesReceived(const QVariantList &messages); void newMessageReceived(const QString &chatId, const QVariantMap &message); + void copyToDownloadsSuccessful(const QString &fileName, const QString &filePath); + void copyToDownloadsError(const QString &fileName, const QString &filePath); public slots: void handleVersionDetected(const QString &version); diff --git a/translations/harbour-fernschreiber-de.ts b/translations/harbour-fernschreiber-de.ts index ed5e208..bd08fcf 100644 --- a/translations/harbour-fernschreiber-de.ts +++ b/translations/harbour-fernschreiber-de.ts @@ -76,6 +76,17 @@ + + BackgroundProgressIndicator + + %1 % + + + + %1 + + + ChatPage @@ -178,6 +189,21 @@ + + ImagePage + + Download Picture + + + + Download of %1 successful. + + + + Download failed. + + + InitializationPage diff --git a/translations/harbour-fernschreiber.ts b/translations/harbour-fernschreiber.ts index ed5e208..bd08fcf 100644 --- a/translations/harbour-fernschreiber.ts +++ b/translations/harbour-fernschreiber.ts @@ -76,6 +76,17 @@ + + BackgroundProgressIndicator + + %1 % + + + + %1 + + + ChatPage @@ -178,6 +189,21 @@ + + ImagePage + + Download Picture + + + + Download of %1 successful. + + + + Download failed. + + + InitializationPage