diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro index ac8118d..417c776 100644 --- a/harbour-fernschreiber.pro +++ b/harbour-fernschreiber.pro @@ -51,6 +51,7 @@ DISTFILES += qml/harbour-fernschreiber.qml \ qml/components/MessageListViewItem.qml \ qml/components/MessageListViewItemSimple.qml \ qml/components/MessageOverlayFlickable.qml \ + qml/components/MultilineEmojiLabel.qml \ qml/components/PinnedMessageItem.qml \ qml/components/PollPreview.qml \ qml/components/PressEffect.qml \ diff --git a/qml/components/MessageListViewItem.qml b/qml/components/MessageListViewItem.qml index dfc05d0..a392514 100644 --- a/qml/components/MessageListViewItem.qml +++ b/qml/components/MessageListViewItem.qml @@ -57,6 +57,8 @@ ListItem { mouseX < (extraContentLoader.x + extraContentLoader.width) && mouseY < (extraContentLoader.y + extraContentLoader.height)) { extraContent.clicked() + } else if (webPagePreviewLoader.item) { + webPagePreviewLoader.item.clicked() } } } @@ -397,17 +399,10 @@ ListItem { active: false asynchronous: true width: parent.width - height: typeof myMessage.content.web_page !== "undefined" ? precalculatedValues.webPagePreviewHeight : 0 + height: (status === Loader.Ready) ? item.implicitHeight : myMessage.content.web_page ? precalculatedValues.webPagePreviewHeight : 0 sourceComponent: Component { - id: webPagePreviewComponent WebPagePreview { - id: webPagePreview - - onImplicitHeightChanged: { - webPagePreviewLoader.height = webPagePreview.implicitHeight; - } - webPageData: myMessage.content.web_page width: parent.width highlighted: messageListItem.highlighted diff --git a/qml/components/MultilineEmojiLabel.qml b/qml/components/MultilineEmojiLabel.qml new file mode 100644 index 0000000..0199af7 --- /dev/null +++ b/qml/components/MultilineEmojiLabel.qml @@ -0,0 +1,78 @@ +/* + Copyright (C) 2020 Slava Monich 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.0 +import Sailfish.Silica 1.0 +import "../js/twemoji.js" as Emoji + +// Combination of maximumLineCount and TruncationMode.Elide (or Fade) breaks +// Emoji image alignment, pushing the image down. This one aligns the image +// correctly on its line. +Label { + property string rawText + property int maxLineCount + + wrapMode: Text.Wrap + textFormat: Text.StyledText + truncationMode: TruncationMode.Elide + + // lineCount is unreliable for StyledText with images and line breaks + readonly property int fontSize: font.pixelSize + readonly property int actualLineHeight: (text === rawText) ? fontSize : (fontSize * 6 / 5) + readonly property int actualLineCount: Math.floor(implicitHeight/actualLineHeight) + + Component.onCompleted: refitText() + onFontSizeChanged: refitText() + onWidthChanged: refitText() + onRawTextChanged: refitText() + onMaxLineCountChanged: refitText() + + function emojify(str) { + return Emoji.emojify(str, fontSize) + } + + function refitText() { + text = emojify(rawText) + if (maxLineCount > 0) { + var divisor = 1 + var max = rawText.length + var min = max + while (actualLineCount > maxLineCount && divisor < rawText.length) { + max = min + divisor++ + min = rawText.length/divisor + text = emojify(rawText.substr(0, min) + "…") + } + while (min < max) { + var mid = Math.floor((min + max)/2) + if (mid === min) { + text = emojify(rawText.substr(0, min) + "…") + break + } else { + text = emojify(rawText.substr(0, mid) + "…") + if (actualLineCount > maxLineCount) { + max = mid + } else { + min = mid + } + } + } + } + } +} diff --git a/qml/components/WebPagePreview.qml b/qml/components/WebPagePreview.qml index a9cfa12..efd4ced 100644 --- a/qml/components/WebPagePreview.qml +++ b/qml/components/WebPagePreview.qml @@ -19,101 +19,86 @@ import QtQuick 2.6 import QtGraphicalEffects 1.0 import Sailfish.Silica 1.0 -import "../components" -import "../js/twemoji.js" as Emoji +import WerkWolf.Fernschreiber 1.0 import "../js/functions.js" as Functions Column { - id: webPagePreviewColumn property var webPageData; - property var pictureFileInformation; - property bool hasImage: false; property bool largerFontSize: false; property bool highlighted + readonly property bool hasImage: picture.fileId !== 0 + readonly property int fontSize: largerFontSize ? Theme.fontSizeSmall : Theme.fontSizeExtraSmall spacing: Theme.paddingSmall Component.onCompleted: { - updateWebPage(); - } - - function updateWebPage() { if (webPageData) { - if (typeof webPageData.photo !== "undefined") { - hasImage = true; + if (webPageData.photo) { // Check first which size fits best... + var photo for (var i = 0; i < webPageData.photo.sizes.length; i++) { - pictureFileInformation = webPageData.photo.sizes[i].photo; + photo = webPageData.photo.sizes[i].photo; if (webPageData.photo.sizes[i].width >= webPagePreviewColumn.width) { break; } } - if (pictureFileInformation.local.is_downloading_completed) { - singleImage.source = pictureFileInformation.local.path; - } else { - tdLibWrapper.downloadFile(pictureFileInformation.id); + if (photo) { + picture.fileInformation = photo } } } } - Connections { - target: tdLibWrapper - onFileUpdated: { - if (typeof pictureFileInformation !== "undefined" && fileId === pictureFileInformation.id) { - if (fileInformation.local.is_downloading_completed) { - pictureFileInformation = fileInformation; - singleImage.source = fileInformation.local.path; - } - } - } + function clicked() { + descriptionText.toggleMaxLineCount() } - Label { + TDLibFile { + id: picture + tdlib: tdLibWrapper + autoLoad: true + } + + MultilineEmojiLabel { id: siteNameText width: parent.width - text: webPageData.site_name ? Emoji.emojify(webPageData.site_name, font.pixelSize) : "" - font.pixelSize: webPagePreviewColumn.largerFontSize ? Theme.fontSizeSmall : Theme.fontSizeExtraSmall + rawText: webPageData.site_name ? webPageData.site_name : "" + font.pixelSize: webPagePreviewColumn.fontSize font.bold: true color: Theme.secondaryHighlightColor - truncationMode: TruncationMode.Fade - maximumLineCount: 1 - textFormat: Text.StyledText - visible: (text !== "") + visible: (rawText !== "") + maxLineCount: 1 } - Label { + MultilineEmojiLabel { id: titleText width: parent.width - text: webPageData.title ? Emoji.emojify(webPageData.title, font.pixelSize) : "" - font.pixelSize: webPagePreviewColumn.largerFontSize ? Theme.fontSizeSmall : Theme.fontSizeExtraSmall + rawText: webPageData.title ? webPageData.title : "" + font.pixelSize: webPagePreviewColumn.fontSize font.bold: true - truncationMode: TruncationMode.Fade - wrapMode: Text.Wrap - maximumLineCount: 2 - textFormat: Text.StyledText - visible: (text !== "") + visible: (rawText !== "") + maxLineCount: 2 } - Label { + MultilineEmojiLabel { id: descriptionText width: parent.width - text: webPageData.description ? Emoji.emojify(Functions.enhanceMessageText(webPageData.description), font.pixelSize) : "" - font.pixelSize: webPagePreviewColumn.largerFontSize ? Theme.fontSizeSmall : Theme.fontSizeExtraSmall - truncationMode: TruncationMode.Fade - wrapMode: Text.Wrap - maximumLineCount: 3 - textFormat: Text.StyledText - visible: (text !== "") - linkColor: Theme.highlightColor + rawText: webPageData.description ? Functions.enhanceMessageText(webPageData.description) : "" + font.pixelSize: webPagePreviewColumn.fontSize + visible: (rawText !== "") + readonly property int defaultMaxLineCount: 3 + maxLineCount: defaultMaxLineCount onLinkActivated: { Functions.handleLink(link); } + function toggleMaxLineCount() { + maxLineCount = maxLineCount > 0 ? 0 : defaultMaxLineCount + } } Item { @@ -133,15 +118,16 @@ Column { fillMode: Image.PreserveAspectCrop autoTransform: true asynchronous: true - visible: hasImage && status === Image.Ready + source: picture.isDownloadingCompleted ? picture.path : "" + visible: opacity > 0 opacity: hasImage && status === Image.Ready ? 1 : 0 layer.enabled: webPagePreviewColumn.highlighted layer.effect: PressEffect { source: singleImage } - Behavior on opacity { NumberAnimation {} } + Behavior on opacity { FadeAnimation {} } MouseArea { anchors.fill: parent onClicked: { - pageStack.push(Qt.resolvedUrl("../pages/ImagePage.qml"), { "photoData" : webPageData.photo, "pictureFileInformation" : pictureFileInformation }); + pageStack.push(Qt.resolvedUrl("../pages/ImagePage.qml"), { "photoData" : webPageData.photo, "pictureFileInformation" : picture.fileInformation }); } } } @@ -154,17 +140,12 @@ Column { } Label { - id: noPreviewAvailableText - width: parent.width text: qsTr("Preview not supported for this link...") font.pixelSize: webPagePreviewColumn.largerFontSize ? Theme.fontSizeExtraSmall : Theme.fontSizeTiny font.italic: true color: Theme.secondaryColor truncationMode: TruncationMode.Fade - wrapMode: Text.Wrap - maximumLineCount: 1 - textFormat: Text.StyledText visible: !siteNameText.visible && !titleText.visible && !descriptionText.visible && !webPagePreviewImageItem.visible }