Добавлена поддержка галереии в постах. #16
21 changed files with 1649 additions and 68 deletions
|
@ -22,6 +22,7 @@ DEFINES += QT_STATICPLUGIN
|
||||||
|
|
||||||
SOURCES += src/harbour-fernschreiber.cpp \
|
SOURCES += src/harbour-fernschreiber.cpp \
|
||||||
src/appsettings.cpp \
|
src/appsettings.cpp \
|
||||||
|
src/boolfiltermodel.cpp \
|
||||||
src/chatpermissionfiltermodel.cpp \
|
src/chatpermissionfiltermodel.cpp \
|
||||||
src/chatlistmodel.cpp \
|
src/chatlistmodel.cpp \
|
||||||
src/chatmodel.cpp \
|
src/chatmodel.cpp \
|
||||||
|
@ -105,14 +106,21 @@ DISTFILES += qml/harbour-fernschreiber.qml \
|
||||||
qml/components/messageContent/MessageGame.qml \
|
qml/components/messageContent/MessageGame.qml \
|
||||||
qml/components/messageContent/MessageLocation.qml \
|
qml/components/messageContent/MessageLocation.qml \
|
||||||
qml/components/messageContent/MessagePhoto.qml \
|
qml/components/messageContent/MessagePhoto.qml \
|
||||||
|
qml/components/messageContent/MessagePhotoAlbum.qml \
|
||||||
qml/components/messageContent/MessagePoll.qml \
|
qml/components/messageContent/MessagePoll.qml \
|
||||||
qml/components/messageContent/MessageSticker.qml \
|
qml/components/messageContent/MessageSticker.qml \
|
||||||
qml/components/messageContent/MessageVenue.qml \
|
qml/components/messageContent/MessageVenue.qml \
|
||||||
|
qml/components/messageContent/MessageVideoAlbum.qml \
|
||||||
qml/components/messageContent/MessageVideoNote.qml \
|
qml/components/messageContent/MessageVideoNote.qml \
|
||||||
qml/components/messageContent/MessageVideo.qml \
|
qml/components/messageContent/MessageVideo.qml \
|
||||||
qml/components/messageContent/MessageVoiceNote.qml \
|
qml/components/messageContent/MessageVoiceNote.qml \
|
||||||
qml/components/messageContent/SponsoredMessage.qml \
|
qml/components/messageContent/SponsoredMessage.qml \
|
||||||
qml/components/messageContent/WebPagePreview.qml \
|
qml/components/messageContent/WebPagePreview.qml \
|
||||||
|
qml/components/messageContent/mediaAlbumPage/FullscreenOverlay.qml \
|
||||||
|
qml/components/messageContent/mediaAlbumPage/PhotoComponent.qml \
|
||||||
|
qml/components/messageContent/mediaAlbumPage/VideoComponent.qml \
|
||||||
|
qml/components/messageContent/mediaAlbumPage/ZoomArea.qml \
|
||||||
|
qml/components/messageContent/mediaAlbumPage/ZoomImage.qml \
|
||||||
qml/components/settingsPage/Accordion.qml \
|
qml/components/settingsPage/Accordion.qml \
|
||||||
qml/components/settingsPage/AccordionItem.qml \
|
qml/components/settingsPage/AccordionItem.qml \
|
||||||
qml/components/settingsPage/ResponsiveGrid.qml \
|
qml/components/settingsPage/ResponsiveGrid.qml \
|
||||||
|
@ -130,6 +138,7 @@ DISTFILES += qml/harbour-fernschreiber.qml \
|
||||||
qml/pages/CoverPage.qml \
|
qml/pages/CoverPage.qml \
|
||||||
qml/pages/DebugPage.qml \
|
qml/pages/DebugPage.qml \
|
||||||
qml/pages/InitializationPage.qml \
|
qml/pages/InitializationPage.qml \
|
||||||
|
qml/pages/MediaAlbumPage.qml \
|
||||||
qml/pages/NewChatPage.qml \
|
qml/pages/NewChatPage.qml \
|
||||||
qml/pages/OverviewPage.qml \
|
qml/pages/OverviewPage.qml \
|
||||||
qml/pages/AboutPage.qml \
|
qml/pages/AboutPage.qml \
|
||||||
|
@ -212,6 +221,7 @@ INSTALLS += telegram 86.png 108.png 128.png 172.png 256.png \
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
src/appsettings.h \
|
src/appsettings.h \
|
||||||
|
src/boolfiltermodel.h \
|
||||||
src/chatpermissionfiltermodel.h \
|
src/chatpermissionfiltermodel.h \
|
||||||
src/chatlistmodel.h \
|
src/chatlistmodel.h \
|
||||||
src/chatmodel.h \
|
src/chatmodel.h \
|
||||||
|
|
|
@ -32,6 +32,7 @@ ListItem {
|
||||||
property int messageIndex
|
property int messageIndex
|
||||||
property int messageViewCount
|
property int messageViewCount
|
||||||
property var myMessage
|
property var myMessage
|
||||||
|
property var messageAlbumMessageIds
|
||||||
property var reactions
|
property var reactions
|
||||||
property bool canReplyToMessage
|
property bool canReplyToMessage
|
||||||
readonly property bool isAnonymous: myMessage.sender_id["@type"] === "messageSenderChat"
|
readonly property bool isAnonymous: myMessage.sender_id["@type"] === "messageSenderChat"
|
||||||
|
@ -68,7 +69,7 @@ ListItem {
|
||||||
property var chatReactions
|
property var chatReactions
|
||||||
property var messageReactions
|
property var messageReactions
|
||||||
|
|
||||||
highlighted: (down || isSelected || additionalOptionsOpened || wasNavigatedTo) && !menuOpen
|
highlighted: (down || (isSelected && messageAlbumMessageIds.length === 0) || additionalOptionsOpened || wasNavigatedTo) && !menuOpen
|
||||||
openMenuOnPressAndHold: !messageListItem.precalculatedValues.pageIsSelecting
|
openMenuOnPressAndHold: !messageListItem.precalculatedValues.pageIsSelecting
|
||||||
|
|
||||||
signal replyToMessage()
|
signal replyToMessage()
|
||||||
|
@ -268,20 +269,20 @@ ListItem {
|
||||||
Connections {
|
Connections {
|
||||||
target: chatModel
|
target: chatModel
|
||||||
onMessagesReceived: {
|
onMessagesReceived: {
|
||||||
messageBackground.isUnread = index > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
|
messageBackground.isUnread = messageIndex > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
|
||||||
}
|
}
|
||||||
onMessagesIncrementalUpdate: {
|
onMessagesIncrementalUpdate: {
|
||||||
messageBackground.isUnread = index > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
|
messageBackground.isUnread = messageIndex > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
|
||||||
}
|
}
|
||||||
onNewMessageReceived: {
|
onNewMessageReceived: {
|
||||||
messageBackground.isUnread = index > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
|
messageBackground.isUnread = messageIndex > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
|
||||||
}
|
}
|
||||||
onUnreadCountUpdated: {
|
onUnreadCountUpdated: {
|
||||||
messageBackground.isUnread = index > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
|
messageBackground.isUnread = messageIndex > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
|
||||||
}
|
}
|
||||||
onLastReadSentMessageUpdated: {
|
onLastReadSentMessageUpdated: {
|
||||||
Debug.log("[ChatModel] Messages in this chat were read, new last read: ", lastReadSentIndex, ", updating description for index ", index, ", status: ", (index <= lastReadSentIndex));
|
Debug.log("[ChatModel] Messages in this chat were read, new last read: ", lastReadSentIndex, ", updating description for index ", index, ", status: ", (messageIndex <= lastReadSentIndex));
|
||||||
messageDateText.text = getMessageStatusText(myMessage, index, lastReadSentIndex, messageDateText.useElapsed);
|
messageDateText.text = getMessageStatusText(myMessage, messageIndex, lastReadSentIndex, messageDateText.useElapsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +303,7 @@ ListItem {
|
||||||
pageStack.currentPage === chatPage) {
|
pageStack.currentPage === chatPage) {
|
||||||
Debug.log("Available reactions for this message: " + reactions);
|
Debug.log("Available reactions for this message: " + reactions);
|
||||||
messageListItem.messageReactions = reactions;
|
messageListItem.messageReactions = reactions;
|
||||||
showItemCompletelyTimer.requestedIndex = index;
|
showItemCompletelyTimer.requestedIndex = messageIndex;
|
||||||
showItemCompletelyTimer.start();
|
showItemCompletelyTimer.start();
|
||||||
} else {
|
} else {
|
||||||
messageListItem.messageReactions = null;
|
messageListItem.messageReactions = null;
|
||||||
|
@ -323,6 +324,13 @@ ListItem {
|
||||||
interval: 200
|
interval: 200
|
||||||
triggeredOnStart: false
|
triggeredOnStart: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
|
if (requestedIndex === messageIndex) {
|
||||||
|
chatView.highlightMoveDuration = -1;
|
||||||
|
chatView.highlightResizeDuration = -1;
|
||||||
|
chatView.scrollToIndex(requestedIndex);
|
||||||
|
chatView.highlightMoveDuration = 0;
|
||||||
|
chatView.highlightResizeDuration = 0;
|
||||||
|
}
|
||||||
Debug.log("Show item completely timer triggered, requested index: " + requestedIndex + ", current index: " + index)
|
Debug.log("Show item completely timer triggered, requested index: " + requestedIndex + ", current index: " + index)
|
||||||
if (requestedIndex === index) {
|
if (requestedIndex === index) {
|
||||||
var p = chatView.contentItem.mapFromItem(reactionsColumn, 0, 0)
|
var p = chatView.contentItem.mapFromItem(reactionsColumn, 0, 0)
|
||||||
|
@ -376,8 +384,10 @@ ListItem {
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (messageListItem.hasContentComponent) {
|
if (messageListItem.hasContentComponent) {
|
||||||
var type = myMessage.content["@type"];
|
var type = myMessage.content["@type"];
|
||||||
|
var albumComponentPart = (myMessage.media_album_id !== "0" && ['messagePhoto', 'messageVideo'].indexOf(type) !== -1) ? 'Album' : '';
|
||||||
|
console.log('delegateComponentLoadingTimer', myMessage.media_album_id, albumComponentPart)
|
||||||
extraContentLoader.setSource(
|
extraContentLoader.setSource(
|
||||||
"../components/messageContent/" + type.charAt(0).toUpperCase() + type.substring(1) + ".qml",
|
"../components/messageContent/" + type.charAt(0).toUpperCase() + type.substring(1) + albumComponentPart + ".qml",
|
||||||
{
|
{
|
||||||
messageListItem: messageListItem
|
messageListItem: messageListItem
|
||||||
})
|
})
|
||||||
|
@ -441,7 +451,7 @@ ListItem {
|
||||||
}
|
}
|
||||||
height: messageTextColumn.height + precalculatedValues.paddingMediumDouble
|
height: messageTextColumn.height + precalculatedValues.paddingMediumDouble
|
||||||
width: precalculatedValues.backgroundWidth
|
width: precalculatedValues.backgroundWidth
|
||||||
property bool isUnread: index > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage"
|
property bool isUnread: messageIndex > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage"
|
||||||
color: Theme.colorScheme === Theme.LightOnDark ? (isUnread ? Theme.secondaryHighlightColor : Theme.secondaryColor) : (isUnread ? Theme.backgroundGlowColor : Theme.overlayBackgroundColor)
|
color: Theme.colorScheme === Theme.LightOnDark ? (isUnread ? Theme.secondaryHighlightColor : Theme.secondaryColor) : (isUnread ? Theme.backgroundGlowColor : Theme.overlayBackgroundColor)
|
||||||
radius: parent.width / 50
|
radius: parent.width / 50
|
||||||
opacity: isUnread ? 0.5 : 0.2
|
opacity: isUnread ? 0.5 : 0.2
|
||||||
|
@ -463,7 +473,13 @@ ListItem {
|
||||||
id: userText
|
id: userText
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: messageListItem.isOwnMessage ? qsTr("You") : Emoji.emojify( myMessage['@type'] === "sponsoredMessage" ? tdLibWrapper.getChat(myMessage.sponsor_chat_id).title : ( messageListItem.isAnonymous ? page.chatInformation.title : Functions.getUserName(messageListItem.userInformation) ), font.pixelSize)
|
text: messageListItem.isOwnMessage
|
||||||
|
? qsTr("You")
|
||||||
|
: Emoji.emojify( myMessage['@type'] === "sponsoredMessage"
|
||||||
|
? tdLibWrapper.getChat(myMessage.sponsor_chat_id).title
|
||||||
|
: ( messageListItem.isAnonymous
|
||||||
|
? page.chatInformation.title
|
||||||
|
: Functions.getUserName(messageListItem.userInformation) ), font.pixelSize)
|
||||||
font.pixelSize: Theme.fontSizeExtraSmall
|
font.pixelSize: Theme.fontSizeExtraSmall
|
||||||
font.weight: Font.ExtraBold
|
font.weight: Font.ExtraBold
|
||||||
color: messageListItem.textColor
|
color: messageListItem.textColor
|
||||||
|
@ -646,7 +662,8 @@ ListItem {
|
||||||
id: extraContentLoader
|
id: extraContentLoader
|
||||||
width: parent.width * getContentWidthMultiplier()
|
width: parent.width * getContentWidthMultiplier()
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
height: item ? item.height : (messageListItem.hasContentComponent ? chatView.getContentComponentHeight(model.content_type, myMessage.content, width) : 0)
|
readonly property var defaultExtraContentHeight: messageListItem.hasContentComponent ? chatView.getContentComponentHeight(model.content_type, myMessage.content, width, model.album_message_ids.length) : 0
|
||||||
|
height: item ? item.height : defaultExtraContentHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
|
@ -671,7 +688,7 @@ ListItem {
|
||||||
running: true
|
running: true
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
messageDateText.text = getMessageStatusText(myMessage, index, chatView.lastReadSentIndex, messageDateText.useElapsed);
|
messageDateText.text = getMessageStatusText(myMessage, messageIndex, chatView.lastReadSentIndex, messageDateText.useElapsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,13 +701,13 @@ ListItem {
|
||||||
font.pixelSize: Theme.fontSizeTiny
|
font.pixelSize: Theme.fontSizeTiny
|
||||||
color: messageListItem.isOwnMessage ? Theme.secondaryHighlightColor : Theme.secondaryColor
|
color: messageListItem.isOwnMessage ? Theme.secondaryHighlightColor : Theme.secondaryColor
|
||||||
horizontalAlignment: messageListItem.textAlign
|
horizontalAlignment: messageListItem.textAlign
|
||||||
text: getMessageStatusText(myMessage, index, chatView.lastReadSentIndex, messageDateText.useElapsed)
|
text: getMessageStatusText(myMessage, messageIndex, chatView.lastReadSentIndex, messageDateText.useElapsed)
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: !messageListItem.precalculatedValues.pageIsSelecting
|
enabled: !messageListItem.precalculatedValues.pageIsSelecting
|
||||||
onClicked: {
|
onClicked: {
|
||||||
messageDateText.useElapsed = !messageDateText.useElapsed;
|
messageDateText.useElapsed = !messageDateText.useElapsed;
|
||||||
messageDateText.text = getMessageStatusText(myMessage, index, chatView.lastReadSentIndex, messageDateText.useElapsed);
|
messageDateText.text = getMessageStatusText(myMessage, messageIndex, chatView.lastReadSentIndex, messageDateText.useElapsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ Loader {
|
||||||
id: loader
|
id: loader
|
||||||
property var minithumbnail
|
property var minithumbnail
|
||||||
property bool highlighted
|
property bool highlighted
|
||||||
|
property int fillMode: tdLibImage.fillMode
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: !!minithumbnail
|
active: !!minithumbnail
|
||||||
sourceComponent: Component {
|
sourceComponent: Component {
|
||||||
|
@ -32,7 +33,7 @@ Loader {
|
||||||
id: minithumbnailImage
|
id: minithumbnailImage
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: "data:image/jpg;base64,"+minithumbnail.data
|
source: "data:image/jpg;base64,"+minithumbnail.data
|
||||||
fillMode: tdLibImage.fillMode
|
fillMode: loader.fillMode
|
||||||
opacity: status === Image.Ready ? 1.0 : 0.0
|
opacity: status === Image.Ready ? 1.0 : 0.0
|
||||||
cache: false
|
cache: false
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
|
@ -43,12 +44,12 @@ Loader {
|
||||||
effect: PressEffect { source: minithumbnailImage }
|
effect: PressEffect { source: minithumbnailImage }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// this had a visible impact on performance
|
||||||
FastBlur {
|
// FastBlur {
|
||||||
anchors.fill: parent
|
// anchors.fill: parent
|
||||||
source: minithumbnailImage
|
// source: minithumbnailImage
|
||||||
radius: Theme.paddingLarge
|
// radius: Theme.paddingLarge
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ Item {
|
||||||
|
|
||||||
readonly property bool hasVisibleThumbnail: thumbnailImage.opacity !== 1.0
|
readonly property bool hasVisibleThumbnail: thumbnailImage.opacity !== 1.0
|
||||||
&& !(videoThumbnailLoader.item && videoThumbnailLoader.item.opacity === 1.0)
|
&& !(videoThumbnailLoader.item && videoThumbnailLoader.item.opacity === 1.0)
|
||||||
|
property alias fillMode: thumbnailImage.fillMode
|
||||||
layer {
|
layer {
|
||||||
enabled: highlighted
|
enabled: highlighted
|
||||||
effect: PressEffect { source: tdlibThumbnail }
|
effect: PressEffect { source: tdlibThumbnail }
|
||||||
|
@ -67,6 +67,7 @@ Item {
|
||||||
|
|
||||||
TDLibMinithumbnail {
|
TDLibMinithumbnail {
|
||||||
id: minithumbnailLoader
|
id: minithumbnailLoader
|
||||||
|
fillMode: thumbnailImage.fillMode
|
||||||
active: !!minithumbnail && thumbnailImage.opacity < 1.0
|
active: !!minithumbnail && thumbnailImage.opacity < 1.0
|
||||||
}
|
}
|
||||||
BackgroundImage {
|
BackgroundImage {
|
||||||
|
@ -103,6 +104,7 @@ Item {
|
||||||
sourceSize.width: width
|
sourceSize.width: width
|
||||||
sourceSize.height: height
|
sourceSize.height: height
|
||||||
mimeType: tdlibThumbnail.videoMimeType
|
mimeType: tdlibThumbnail.videoMimeType
|
||||||
|
fillMode: thumbnailImage.fillMode == Image.PreserveAspectFit ? Thumbnail.PreserveAspectFit : Thumbnail.PreserveAspectCrop
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
opacity: status === Thumbnail.Ready ? 1.0 : 0.0
|
opacity: status === Thumbnail.Ready ? 1.0 : 0.0
|
||||||
Behavior on opacity { FadeAnimation {} }
|
Behavior on opacity { FadeAnimation {} }
|
||||||
|
|
|
@ -20,7 +20,6 @@ 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/functions.js" as Functions
|
|
||||||
import "../../js/debug.js" as Debug
|
import "../../js/debug.js" as Debug
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|
|
@ -22,28 +22,25 @@ import "../"
|
||||||
|
|
||||||
MessageContentBase {
|
MessageContentBase {
|
||||||
|
|
||||||
function calculateBiggest() {
|
height: Math.max(Theme.itemSizeExtraSmall, Math.min(Math.round(width * 0.66666666), width / getAspectRatio()))
|
||||||
var candidateBiggest = rawMessage.content.photo.sizes[rawMessage.content.photo.sizes.length - 1];
|
readonly property alias photoData: photo.photo;
|
||||||
if (candidateBiggest.width === 0 && rawMessage.content.photo.sizes.length > 1) {
|
|
||||||
for (var i = (rawMessage.content.photo.sizes.length - 2); i >= 0; i--) {
|
onClicked: {
|
||||||
candidateBiggest = rawMessage.content.photo.sizes[i];
|
pageStack.push(Qt.resolvedUrl("../../pages/MediaAlbumPage.qml"), {
|
||||||
if (candidateBiggest.width > 0) {
|
"messages" : [rawMessage],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function getAspectRatio() {
|
||||||
|
var candidate = photoData.sizes[photoData.sizes.length - 1];
|
||||||
|
if (candidate.width === 0 && photoData.sizes.length > 1) {
|
||||||
|
for (var i = (photoData.sizes.length - 2); i >= 0; i--) {
|
||||||
|
candidate = photoData.sizes[i];
|
||||||
|
if (candidate.width > 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return candidateBiggest;
|
return candidate.width / candidate.height;
|
||||||
}
|
|
||||||
|
|
||||||
height: Math.max(Theme.itemSizeExtraSmall, Math.min(defaultHeight, width / (biggest.width/biggest.height)))
|
|
||||||
readonly property int defaultHeight: Math.round(width * 0.66666666)
|
|
||||||
readonly property var biggest: calculateBiggest();
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
pageStack.push(Qt.resolvedUrl("../../pages/ImagePage.qml"), {
|
|
||||||
"photoData" : photo.photo,
|
|
||||||
// "pictureFileInformation" : photo.fileInformation
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
TDLibPhoto {
|
TDLibPhoto {
|
||||||
id: photo
|
id: photo
|
||||||
|
@ -51,7 +48,4 @@ MessageContentBase {
|
||||||
photo: rawMessage.content.photo
|
photo: rawMessage.content.photo
|
||||||
highlighted: parent.highlighted
|
highlighted: parent.highlighted
|
||||||
}
|
}
|
||||||
BackgroundImage {
|
|
||||||
visible: !rawMessage.content.photo.minithumbnail && photo.image.status !== Image.Ready
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
207
qml/components/messageContent/MessagePhotoAlbum.qml
Normal file
207
qml/components/messageContent/MessagePhotoAlbum.qml
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
/*
|
||||||
|
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 "../"
|
||||||
|
|
||||||
|
MessageContentBase {
|
||||||
|
id: messageContent
|
||||||
|
property string chatId
|
||||||
|
readonly property int heightUnit: Math.round(width * 0.66666666)
|
||||||
|
readonly property var albumId: rawMessage.media_album_id
|
||||||
|
property var albumMessageIds: messageListItem ? messageListItem.messageAlbumMessageIds : []//overlayFlickable.messageAlbumMessageIds
|
||||||
|
onAlbumMessageIdsChanged: albumMessages = getMessages() //chatModel.getMessagesForAlbum(messageContent.albumId)
|
||||||
|
property var albumMessages: getMessages()//chatModel.getMessagesForAlbum(messageContent.albumId)
|
||||||
|
property bool firstLarge: albumMessages.length % 2 !== 0;
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
height: defaultExtraContentHeight//(firstLarge ? heightUnit * 0.75 : 0 ) + heightUnit * 0.25 * albumMessageIds.length
|
||||||
|
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if(messageListItem.precalculatedValues.pageIsSelecting) {
|
||||||
|
page.toggleMessageSelection(rawMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openDetail(-1);
|
||||||
|
}
|
||||||
|
function getMessages() {
|
||||||
|
var msgs = [rawMessage];
|
||||||
|
if(messageContent.albumId === '0' || messageContent.albumMessageIds.length < 2) {
|
||||||
|
return msgs;
|
||||||
|
}
|
||||||
|
// var othermsgIds =
|
||||||
|
// getMessages from tdlib isn't faster
|
||||||
|
// if(rawMessage && rawMessage.chat_id) {
|
||||||
|
// var messages = [];
|
||||||
|
// return albumMessageIds.map(function(msgId){
|
||||||
|
// if(msgId === rawMessage.id) {
|
||||||
|
// return rawMessage;
|
||||||
|
// }
|
||||||
|
// return tdLibWrapper.getMessage(rawMessage.chat_id, msgId);
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
chatModel.getMessagesForAlbum(messageContent.albumId, 1).forEach(function(msg){
|
||||||
|
msgs.push(msg);
|
||||||
|
});
|
||||||
|
//
|
||||||
|
return msgs; //chatModel.getMessagesForAlbum(messageContent.albumId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDetail(index) {
|
||||||
|
console.log('open detail', index || 0);
|
||||||
|
|
||||||
|
|
||||||
|
pageStack.push(Qt.resolvedUrl("../../pages/MediaAlbumPage.qml"), {
|
||||||
|
"messages" : albumMessages,
|
||||||
|
"index": index || 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Connections { // TODO: needed?
|
||||||
|
target: tdLibWrapper
|
||||||
|
|
||||||
|
onReceivedMessage: {
|
||||||
|
if (albumMessageIds.indexOf(messageId)) {
|
||||||
|
// albumMessages = getMessages()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: photoPreviewComponent
|
||||||
|
MessagePhoto {
|
||||||
|
// width: parent.width
|
||||||
|
// height: parent.height
|
||||||
|
messageListItem: messageContent.messageListItem
|
||||||
|
overlayFlickable: messageContent.overlayFlickable
|
||||||
|
rawMessage: albumMessages[modelIndex]
|
||||||
|
highlighted: mediaBackgroundItem.highlighted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: videoPreviewComponent
|
||||||
|
Item {
|
||||||
|
property bool highlighted: mediaBackgroundItem.highlighted
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
TDLibThumbnail {
|
||||||
|
id: tdLibImage
|
||||||
|
width: parent.width //don't use anchors here for easier custom scaling
|
||||||
|
height: parent.height
|
||||||
|
highlighted: parent.highlighted
|
||||||
|
thumbnail: albumMessages[modelIndex].content.video.thumbnail
|
||||||
|
minithumbnail: albumMessages[modelIndex].content.video.minithumbnail
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
anchors {
|
||||||
|
fill: videoIcon
|
||||||
|
leftMargin: -Theme.paddingSmall
|
||||||
|
topMargin: -Theme.paddingSmall
|
||||||
|
bottomMargin: -Theme.paddingSmall
|
||||||
|
rightMargin: -Theme.paddingLarge
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
radius: Theme.paddingSmall
|
||||||
|
color: Theme.rgba(Theme.overlayBackgroundColor, 0.4)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon {
|
||||||
|
id: videoIcon
|
||||||
|
source: "image://theme/icon-m-video"
|
||||||
|
width: Theme.iconSizeSmall
|
||||||
|
height: Theme.iconSizeSmall
|
||||||
|
highlighted: parent.highlighted
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: Theme.paddingSmall
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
id: contentGrid
|
||||||
|
property int firstWidth: firstLarge ? contentGrid.width : normalWidth
|
||||||
|
property int firstHeight: firstLarge ? heightUnit - contentGrid.spacing : normalHeight
|
||||||
|
property int normalWidth: (contentGrid.width - contentGrid.spacing) / 2
|
||||||
|
property int normalHeight: (heightUnit / 2) - contentGrid.spacing
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.paddingMedium
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: albumMessages
|
||||||
|
delegate: BackgroundItem {
|
||||||
|
id: mediaBackgroundItem
|
||||||
|
property bool isLarge: firstLarge && model.index === 0
|
||||||
|
width: model.index === 0 ? contentGrid.firstWidth : contentGrid.normalWidth
|
||||||
|
height: model.index === 0 ? contentGrid.firstHeight : contentGrid.normalHeight
|
||||||
|
|
||||||
|
readonly property bool isSelected: messageListItem.precalculatedValues.pageIsSelecting && page.selectedMessages.some(function(existingMessage) {
|
||||||
|
return existingMessage.id === albumMessages[index].id
|
||||||
|
});
|
||||||
|
highlighted: isSelected || down || messageContent.highlighted
|
||||||
|
onClicked: {
|
||||||
|
if(messageListItem.precalculatedValues.pageIsSelecting) {
|
||||||
|
page.toggleMessageSelection(albumMessages[index]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openDetail(index);
|
||||||
|
}
|
||||||
|
onPressAndHold: {
|
||||||
|
page.toggleMessageSelection(albumMessages[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
// asynchronous: true
|
||||||
|
|
||||||
|
readonly property int modelIndex: index
|
||||||
|
sourceComponent: albumMessages[index].content["@type"] === 'messageVideo' ? videoPreviewComponent : photoPreviewComponent
|
||||||
|
opacity: status === Loader.Ready
|
||||||
|
Behavior on opacity {FadeAnimator{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO video:
|
||||||
|
rawMessage.content.video.thumbnail
|
||||||
|
TDLibPhoto {
|
||||||
|
id: photo
|
||||||
|
anchors.fill: parent
|
||||||
|
photo: rawMessage.content.photo
|
||||||
|
highlighted: parent.highlighted
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
Rectangle {
|
||||||
|
visible: mediaBackgroundItem.isSelected
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
}
|
||||||
|
color: 'transparent'
|
||||||
|
border.color: Theme.highlightColor
|
||||||
|
border.width: Theme.paddingSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,12 @@ import "../../js/debug.js" as Debug
|
||||||
MessageContentBase {
|
MessageContentBase {
|
||||||
id: videoMessageComponent
|
id: videoMessageComponent
|
||||||
|
|
||||||
property var videoData: ( rawMessage.content['@type'] === "messageVideo" ) ? rawMessage.content.video : ( ( rawMessage.content['@type'] === "messageAnimation" ) ? rawMessage.content.animation : rawMessage.content.video_note )
|
property var videoData: ( rawMessage.content['@type'] === "messageVideo" )
|
||||||
|
? rawMessage.content.video
|
||||||
|
: (
|
||||||
|
( rawMessage.content['@type'] === "messageAnimation" )
|
||||||
|
? rawMessage.content.animation
|
||||||
|
: rawMessage.content.video_note )
|
||||||
property string videoUrl;
|
property string videoUrl;
|
||||||
property int previewFileId;
|
property int previewFileId;
|
||||||
property int videoFileId;
|
property int videoFileId;
|
||||||
|
|
19
qml/components/messageContent/MessageVideoAlbum.qml
Normal file
19
qml/components/messageContent/MessageVideoAlbum.qml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
MessagePhotoAlbum {}
|
|
@ -0,0 +1,279 @@
|
||||||
|
/*
|
||||||
|
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 QtGraphicalEffects 1.0
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
import "../../../js/functions.js" as Functions
|
||||||
|
|
||||||
|
|
||||||
|
Item {
|
||||||
|
// id
|
||||||
|
id: overlay
|
||||||
|
// property declarations
|
||||||
|
property int pageCount
|
||||||
|
property int currentIndex
|
||||||
|
property alias text: captionLabel.text
|
||||||
|
property bool active: true
|
||||||
|
property var message
|
||||||
|
readonly property color gradientColor: '#bb000000'
|
||||||
|
readonly property int gradientPadding: Theme.itemSizeMedium
|
||||||
|
// signal declarations
|
||||||
|
// JavaScript functions
|
||||||
|
// object properties
|
||||||
|
anchors.fill: parent
|
||||||
|
opacity: active ? 1 : 0
|
||||||
|
Behavior on opacity { FadeAnimator {} }
|
||||||
|
// large property bindings
|
||||||
|
// child objects
|
||||||
|
// states
|
||||||
|
// transitions
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
console.log('overlay active', active)
|
||||||
|
}
|
||||||
|
|
||||||
|
function forwardMessage() {
|
||||||
|
var neededPermissions = Functions.getMessagesNeededForwardPermissions([message]);
|
||||||
|
pageStack.push(Qt.resolvedUrl("../../../pages/ChatSelectionPage.qml"), {
|
||||||
|
myUserId: tdLibWrapper.getUserInformation().id,
|
||||||
|
headerDescription: qsTr("Forward %Ln messages", "dialog header", 1),
|
||||||
|
payload: {fromChatId: message.chat_id, messageIds:[message.id], neededPermissions: neededPermissions},
|
||||||
|
state: "forwardMessages"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// "header"
|
||||||
|
|
||||||
|
LinearGradient {
|
||||||
|
id: topGradient
|
||||||
|
property int startY: 0;
|
||||||
|
// Behavior on startY { NumberAnimation {duration: 2000} }
|
||||||
|
start: Qt.point(0, Math.min(height-gradientPadding*2, startY))
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
bottom: closeButton.bottom
|
||||||
|
|
||||||
|
bottomMargin: -gradientPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: gradientColor }
|
||||||
|
GradientStop { position: 1.0; color: 'transparent' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
id: closeButton
|
||||||
|
icon.source: "image://theme/icon-m-cancel?" + (pressed
|
||||||
|
? Theme.highlightColor
|
||||||
|
: Theme.lightPrimaryColor)
|
||||||
|
onClicked: pageStack.pop()
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
margins: Theme.horizontalPageMargin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SilicaFlickable {
|
||||||
|
id: captionFlickable
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
// leftMargin: Theme.horizontalPageMargin
|
||||||
|
right: closeButton.left
|
||||||
|
top: parent.top
|
||||||
|
// topMargin: Theme.horizontalPageMargin
|
||||||
|
}
|
||||||
|
interactive: captionLabel.expanded && contentHeight > height
|
||||||
|
clip: true
|
||||||
|
height: Math.min(contentHeight, parent.height / 4)
|
||||||
|
contentHeight: captionLabel.height + Theme.horizontalPageMargin
|
||||||
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
VerticalScrollDecorator {
|
||||||
|
opacity: visible ? 1.0 : 0.0
|
||||||
|
flickable: captionFlickable
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: captionLabel
|
||||||
|
property bool expandable: expanded || height < contentHeight
|
||||||
|
property bool expanded
|
||||||
|
|
||||||
|
height: text ?
|
||||||
|
expanded
|
||||||
|
? contentHeight
|
||||||
|
: Theme.itemSizeMedium
|
||||||
|
: 0;
|
||||||
|
// maximumLineCount: expanded ? 0 : 3
|
||||||
|
color: Theme.primaryColor
|
||||||
|
// text: model.modelData.content.caption.text
|
||||||
|
text: Emoji.emojify(Functions.enhanceMessageText(message.content.caption, false), Theme.fontSizeExtraSmall)
|
||||||
|
onTextChanged: expanded = false
|
||||||
|
font.pixelSize: Theme.fontSizeExtraSmall
|
||||||
|
wrapMode: Text.WrapAnywhere
|
||||||
|
bottomPadding: expanded ? Theme.paddingLarge : 0
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: Theme.horizontalPageMargin
|
||||||
|
rightMargin: Theme.paddingLarge
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
topMargin: Theme.horizontalPageMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on height { NumberAnimation {duration: 300} }
|
||||||
|
Behavior on text {
|
||||||
|
SequentialAnimation {
|
||||||
|
FadeAnimation {
|
||||||
|
target: captionLabel
|
||||||
|
to: 0.0
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
|
PropertyAction {}
|
||||||
|
FadeAnimation {
|
||||||
|
target: captionLabel
|
||||||
|
to: 1.0
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
OpacityRampEffect {
|
||||||
|
sourceItem: captionLabel
|
||||||
|
enabled: !captionLabel.expanded
|
||||||
|
direction: OpacityRamp.TopToBottom
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: captionLabel
|
||||||
|
enabled: captionLabel.expandable
|
||||||
|
onClicked: {
|
||||||
|
captionLabel.expanded = !captionLabel.expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "footer"
|
||||||
|
LinearGradient {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
top: buttons.top
|
||||||
|
bottom: parent.bottom
|
||||||
|
topMargin: -gradientPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: 'transparent' }
|
||||||
|
GradientStop { position: 1.0; color: gradientColor }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
asynchronous: true
|
||||||
|
active: overlay.pageCount > 1
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
verticalCenter: buttons.bottom
|
||||||
|
}
|
||||||
|
sourceComponent: Component {
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: pageIndicatorRow
|
||||||
|
height: Theme.paddingSmall
|
||||||
|
spacing: height
|
||||||
|
Repeater {
|
||||||
|
id: pageIndicator
|
||||||
|
model: overlay.pageCount
|
||||||
|
Rectangle {
|
||||||
|
property bool active: model.index === overlay.currentIndex
|
||||||
|
width: pageIndicatorRow.height
|
||||||
|
height: pageIndicatorRow.height
|
||||||
|
color: active ? Theme.lightPrimaryColor : Theme.rgba(Theme.lightSecondaryColor, Theme.opacityLow)
|
||||||
|
Behavior on color { ColorAnimation {} }
|
||||||
|
radius: Theme.paddingSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: buttons
|
||||||
|
height: Theme.itemSizeSmall
|
||||||
|
width: childrenRect.width
|
||||||
|
spacing: Theme.paddingLarge
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
bottom: parent.bottom
|
||||||
|
bottomMargin: Theme.paddingLarge
|
||||||
|
}
|
||||||
|
|
||||||
|
// IconButton {
|
||||||
|
// icon.source: "image://theme/icon-m-cancel?" + (pressed
|
||||||
|
// ? Theme.highlightColor
|
||||||
|
// : Theme.lightPrimaryColor)
|
||||||
|
// onClicked: pageStack.pop()
|
||||||
|
|
||||||
|
// }
|
||||||
|
IconButton {
|
||||||
|
icon.source: "image://theme/icon-m-downloads?" + (pressed
|
||||||
|
? Theme.highlightColor
|
||||||
|
: Theme.lightPrimaryColor)
|
||||||
|
onClicked: pageStack.pop()
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
width: Theme.itemSizeSmall
|
||||||
|
height: Theme.itemSizeSmall
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
enabled: message.can_be_forwarded
|
||||||
|
opacity: enabled ? 1.0 : 0.2
|
||||||
|
icon.source: "image://theme/icon-m-share?" + (pressed
|
||||||
|
? Theme.highlightColor
|
||||||
|
: Theme.lightPrimaryColor)
|
||||||
|
onClicked: forwardMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: 'hasCaption'
|
||||||
|
when: captionLabel.height > 0
|
||||||
|
PropertyChanges { target: topGradient;
|
||||||
|
startY: captionFlickable.height
|
||||||
|
}
|
||||||
|
AnchorChanges {
|
||||||
|
target: topGradient
|
||||||
|
// anchors.top: captionLabel.verticalCenter
|
||||||
|
anchors.bottom: captionFlickable.bottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
transitions:
|
||||||
|
Transition {
|
||||||
|
AnchorAnimation { duration: 200 }
|
||||||
|
NumberAnimation { properties: "startY"; duration: 200 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
import QtQuick 2.6
|
||||||
|
|
||||||
|
ZoomImage {
|
||||||
|
photoData: model.modelData.content.photo
|
||||||
|
onClicked: {
|
||||||
|
console.log('clicked', zoomed)
|
||||||
|
if(zoomed) {
|
||||||
|
zoomOut(true)
|
||||||
|
page.overlayActive = true
|
||||||
|
} else {
|
||||||
|
page.overlayActive = !page.overlayActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
181
qml/components/messageContent/mediaAlbumPage/VideoComponent.qml
Normal file
181
qml/components/messageContent/mediaAlbumPage/VideoComponent.qml
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
import QtQuick 2.6
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
import WerkWolf.Fernschreiber 1.0
|
||||||
|
import QtMultimedia 5.6
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import "../../"
|
||||||
|
|
||||||
|
Video {
|
||||||
|
id: video
|
||||||
|
property var videoData: model.modelData.content.video
|
||||||
|
readonly property bool isPlaying: playbackState === MediaPlayer.PlayingState
|
||||||
|
readonly property bool isCurrent: index === page.index
|
||||||
|
property bool shouldPlay
|
||||||
|
autoLoad: true
|
||||||
|
source: file.isDownloadingCompleted ? file.path : ''
|
||||||
|
onIsCurrentChanged: {
|
||||||
|
if(!isCurrent) {
|
||||||
|
pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onStatusChanged: {
|
||||||
|
if(status === MediaPlayer.EndOfMedia) {
|
||||||
|
page.overlayActive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TDLibThumbnail {
|
||||||
|
id: tdLibImage
|
||||||
|
|
||||||
|
property bool active: !file.isDownloadingCompleted || (!video.isPlaying && (video.position === 0 || video.status === MediaPlayer.EndOfMedia))
|
||||||
|
opacity: active ? 1 : 0
|
||||||
|
visible: active || opacity > 0
|
||||||
|
|
||||||
|
width: parent.width //don't use anchors here for easier custom scaling
|
||||||
|
height: parent.height
|
||||||
|
// highlighted: parent.highlighted
|
||||||
|
thumbnail: videoData.thumbnail
|
||||||
|
minithumbnail: videoData.minithumbnail
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TDLibFile {
|
||||||
|
id: file
|
||||||
|
autoLoad: false
|
||||||
|
tdlib: tdLibWrapper
|
||||||
|
fileInformation: videoData.video
|
||||||
|
property real progress: isDownloadingCompleted ? 1.0 : (downloadedSize / size)
|
||||||
|
onDownloadingCompletedChanged: {
|
||||||
|
if(isDownloadingCompleted) {
|
||||||
|
video.source = file.path
|
||||||
|
if(video.shouldPlay) {
|
||||||
|
video.play()
|
||||||
|
delayedOverlayHide.start()
|
||||||
|
video.shouldPlay = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: 'dl: '+file.downloadedSize
|
||||||
|
+ ' \ns: '+file.size
|
||||||
|
+ ' \nes: '+file.expectedSize
|
||||||
|
+ ' \nd:'+file.isDownloadingActive
|
||||||
|
+ ' \nc:'+file.isDownloadingCompleted
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: page.overlayActive = !page.overlayActive
|
||||||
|
}
|
||||||
|
|
||||||
|
RadialGradient { // white videos = invisible button. I can't tell since which SFOS version the opaque button is available, so:
|
||||||
|
id: buttonBg
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Theme.itemSizeLarge; height: Theme.itemSizeLarge
|
||||||
|
property color baseColor: Theme.rgba(palette.overlayBackgroundColor, 0.2)
|
||||||
|
|
||||||
|
enabled: videoUI.active || !file.isDownloadingCompleted
|
||||||
|
opacity: enabled ? 1 : 0
|
||||||
|
Behavior on opacity { FadeAnimator {} }
|
||||||
|
gradient: Gradient {
|
||||||
|
|
||||||
|
GradientStop { position: 0.0; color: buttonBg.baseColor }
|
||||||
|
GradientStop { position: 0.3; color: buttonBg.baseColor }
|
||||||
|
GradientStop { position: 0.5; color: 'transparent' }
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
anchors.fill: parent
|
||||||
|
icon.source: "image://theme/icon-l-"+(video.isPlaying || video.shouldPlay ? 'pause' : 'play')+"?" + (pressed
|
||||||
|
? Theme.highlightColor
|
||||||
|
: Theme.lightPrimaryColor)
|
||||||
|
onClicked: {
|
||||||
|
if (!file.isDownloadingCompleted) {
|
||||||
|
video.shouldPlay = !video.shouldPlay;
|
||||||
|
if(video.shouldPlay) {
|
||||||
|
file.load()
|
||||||
|
} else {
|
||||||
|
file.cancel()
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video.isPlaying) {
|
||||||
|
video.pause()
|
||||||
|
} else {
|
||||||
|
video.play()
|
||||||
|
delayedOverlayHide.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressCircle {
|
||||||
|
property bool active: file.isDownloadingActive
|
||||||
|
opacity: active ? 1 : 0
|
||||||
|
Behavior on opacity { FadeAnimator {} }
|
||||||
|
anchors.centerIn: parent
|
||||||
|
value: file.progress
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
id: videoUI
|
||||||
|
property bool active: overlay.active// && file.isDownloadingCompleted
|
||||||
|
anchors.fill: parent
|
||||||
|
opacity: active ? 1 : 0
|
||||||
|
Behavior on opacity { FadeAnimator {} }
|
||||||
|
|
||||||
|
Slider {
|
||||||
|
id: slider
|
||||||
|
value: video.position
|
||||||
|
minimumValue: 0
|
||||||
|
maximumValue: video.duration || 0.1
|
||||||
|
enabled: parent.active && video.seekable
|
||||||
|
width: parent.width
|
||||||
|
handleVisible: false
|
||||||
|
animateValue: true
|
||||||
|
stepSize: 500
|
||||||
|
anchors {
|
||||||
|
bottom: parent.bottom
|
||||||
|
bottomMargin: Theme.itemSizeMedium
|
||||||
|
}
|
||||||
|
valueText: value > 0 || down ? Format.formatDuration((value)/1000, Formatter.Duration) : ''
|
||||||
|
leftMargin: Theme.horizontalPageMargin
|
||||||
|
rightMargin: Theme.horizontalPageMargin
|
||||||
|
onDownChanged: {
|
||||||
|
if(!down) {
|
||||||
|
video.seek(value)
|
||||||
|
value = Qt.binding(function() { return video.position })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: Theme.horizontalPageMargin
|
||||||
|
bottom: parent.bottom
|
||||||
|
topMargin: Theme.paddingSmall
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeExtraSmall
|
||||||
|
text: file.isDownloadingCompleted
|
||||||
|
? Format.formatDuration((parent.maximumValue - parent.value)/1000, Formatter.Duration)
|
||||||
|
: (video.videoData.duration
|
||||||
|
? Format.formatDuration(video.videoData.duration, Formatter.Duration) + ', '
|
||||||
|
: '') + Format.formatFileSize(file.size || file.expectedSize)
|
||||||
|
color: Theme.secondaryColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: delayedOverlayHide
|
||||||
|
interval: 500
|
||||||
|
onTriggered: {
|
||||||
|
if(video.isPlaying) {
|
||||||
|
page.overlayActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
148
qml/components/messageContent/mediaAlbumPage/ZoomArea.qml
Normal file
148
qml/components/messageContent/mediaAlbumPage/ZoomArea.qml
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import QtQuick 2.6
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
|
SilicaFlickable {
|
||||||
|
// id
|
||||||
|
id: flickable
|
||||||
|
// property declarations
|
||||||
|
property real zoom
|
||||||
|
property bool zoomed
|
||||||
|
// override if needed
|
||||||
|
property bool zoomEnabled: true
|
||||||
|
property real minimumZoom: fitZoom
|
||||||
|
property real maximumZoom: 4 //Math.max(fitZoom, 1) * 3
|
||||||
|
|
||||||
|
default property alias zoomContentItem: zoomContentItem.data
|
||||||
|
property alias implicitContentWidth: zoomContentItem.implicitWidth
|
||||||
|
property alias implicitContentHeight: zoomContentItem.implicitHeight
|
||||||
|
// factor for "PreserveAspectFit"
|
||||||
|
readonly property real fitZoom: implicitContentWidth > 0 && implicitContentHeight > 0
|
||||||
|
? Math.min(maximumZoom, width / implicitContentWidth, height / implicitContentHeight)
|
||||||
|
: 1.0
|
||||||
|
readonly property int minimumBoundaryAxis: (implicitContentWidth / implicitContentHeight) > (width / height) ? Qt.Horizontal : Qt.Vertical
|
||||||
|
|
||||||
|
// JavaScript functions
|
||||||
|
function zoomOut(animated) {
|
||||||
|
if (zoomed) {
|
||||||
|
if(animated) { zoomOutAnimation.start() }
|
||||||
|
else {
|
||||||
|
zoom = fitZoom
|
||||||
|
zoomed = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// object properties
|
||||||
|
contentWidth: Math.max(width, zoomContentItem.width)
|
||||||
|
contentHeight: Math.max(height, zoomContentItem.height)
|
||||||
|
enabled: !zoomOutAnimation.running && implicitContentWidth > 0 && implicitContentHeight > 0
|
||||||
|
flickableDirection: Flickable.HorizontalAndVerticalFlick
|
||||||
|
interactive: zoomed
|
||||||
|
// According to Jolla, otherwise pinching would sometimes not work:
|
||||||
|
pressDelay: 0
|
||||||
|
Binding { // Update zoom on orientation changes and set as default
|
||||||
|
target: flickable
|
||||||
|
when: !zoomed
|
||||||
|
property: "zoom"
|
||||||
|
value: minimumZoom
|
||||||
|
}
|
||||||
|
// child objects
|
||||||
|
|
||||||
|
PinchArea {
|
||||||
|
id: pinchArea
|
||||||
|
parent: flickable.contentItem
|
||||||
|
width: flickable.contentWidth
|
||||||
|
height: flickable.contentHeight
|
||||||
|
enabled: zoomEnabled && minimumZoom !== maximumZoom && flickable.enabled
|
||||||
|
onPinchUpdated: {
|
||||||
|
scrollDecoratorTimer.restart()
|
||||||
|
var f = flickable;
|
||||||
|
var requestedZoomFactor = 1.0 + pinch.scale - pinch.previousScale;
|
||||||
|
var previousWidth = f.contentWidth
|
||||||
|
var previousHeight = f.contentHeight
|
||||||
|
var targetWidth
|
||||||
|
var targetHeight
|
||||||
|
var targetZoom = requestedZoomFactor * f.zoom;
|
||||||
|
if (targetZoom < f.minimumZoom) {
|
||||||
|
f.zoom = f.minimumZoom;
|
||||||
|
f.zoomed = false;
|
||||||
|
f.contentX = 0;
|
||||||
|
f.contentY = 0;
|
||||||
|
return
|
||||||
|
} else if(targetZoom >= f.maximumZoom) {
|
||||||
|
f.zoom = f.maximumZoom;
|
||||||
|
targetHeight = f.implicitContentHeight * f.zoom
|
||||||
|
targetWidth = f.implicitContentWidth * f.zoom
|
||||||
|
}
|
||||||
|
else if(targetZoom < f.maximumZoom) {
|
||||||
|
if (f.minimumBoundaryAxis == Qt.Horizontal) {
|
||||||
|
targetWidth = f.contentWidth * requestedZoomFactor
|
||||||
|
f.zoom = targetWidth / f.implicitContentWidth
|
||||||
|
targetHeight = f.implicitContentHeight * f.zoom
|
||||||
|
} else {
|
||||||
|
targetHeight = f.contentHeight * requestedZoomFactor
|
||||||
|
f.zoom = targetHeight / f.implicitContentHeight
|
||||||
|
targetWidth = f.implicitContentWidth * f.zoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// calculate center difference
|
||||||
|
f.contentX += pinch.previousCenter.x - pinch.center.x
|
||||||
|
f.contentY += pinch.previousCenter.y - pinch.center.y
|
||||||
|
// move to new (zoomed) center. this jumps a tiny bit, but is bearable:
|
||||||
|
if (targetWidth > f.width)
|
||||||
|
f.contentX -= (previousWidth - targetWidth)/(previousWidth/pinch.previousCenter.x)
|
||||||
|
if (targetHeight > f.height)
|
||||||
|
f.contentY -= (previousHeight - targetHeight)/(previousHeight/pinch.previousCenter.y)
|
||||||
|
|
||||||
|
f.zoomed = true
|
||||||
|
}
|
||||||
|
onPinchFinished: {
|
||||||
|
returnToBounds()
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
id: zoomContentItem
|
||||||
|
anchors.centerIn: parent
|
||||||
|
implicitWidth: flickable.width
|
||||||
|
implicitHeight: flickable.height
|
||||||
|
width: Math.ceil(implicitWidth * zoom)
|
||||||
|
height: Math.ceil(implicitHeight * zoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// enable zoom to minimumZoom on click
|
||||||
|
ParallelAnimation {
|
||||||
|
id: zoomOutAnimation
|
||||||
|
NumberAnimation {
|
||||||
|
target: flickable
|
||||||
|
properties: "contentX, contentY"
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: flickable
|
||||||
|
property: "zoom"
|
||||||
|
to: fitZoom
|
||||||
|
}
|
||||||
|
onRunningChanged: {
|
||||||
|
if(!running) {
|
||||||
|
zoomed = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// show scroll decorators when scrolling OR zooming
|
||||||
|
Timer {
|
||||||
|
id: scrollDecoratorTimer
|
||||||
|
readonly property bool moving: flickable.moving
|
||||||
|
readonly property bool showing: moving || running
|
||||||
|
onMovingChanged: restart()
|
||||||
|
interval: 300
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalScrollDecorator {
|
||||||
|
flickable: flickable
|
||||||
|
opacity: scrollDecoratorTimer.showing ? 1.0 : 0.0
|
||||||
|
}
|
||||||
|
HorizontalScrollDecorator {
|
||||||
|
flickable: flickable
|
||||||
|
opacity: scrollDecoratorTimer.showing ? 1.0 : 0.0
|
||||||
|
}
|
||||||
|
}
|
127
qml/components/messageContent/mediaAlbumPage/ZoomImage.qml
Normal file
127
qml/components/messageContent/mediaAlbumPage/ZoomImage.qml
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import QtQuick 2.0
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
import WerkWolf.Fernschreiber 1.0
|
||||||
|
import "../../"
|
||||||
|
|
||||||
|
ZoomArea {
|
||||||
|
// id
|
||||||
|
id: zoomArea
|
||||||
|
property var photoData //albumMessages[index].content.photo
|
||||||
|
property bool active: true
|
||||||
|
property alias image: image
|
||||||
|
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
maximumZoom: Math.max(Screen.width, Screen.height) / 200
|
||||||
|
// maximumZoom: Math.max(fitZoom, 1) * 3
|
||||||
|
implicitContentWidth: image.implicitWidth
|
||||||
|
implicitContentHeight: image.implicitHeight
|
||||||
|
zoomEnabled: image.status == Image.Ready
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (!active) {
|
||||||
|
zoomOut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
// var photoData = albumMessages[index].content.photo;
|
||||||
|
if (photoData) {
|
||||||
|
|
||||||
|
var biggestIndex = -1
|
||||||
|
for (var i = 0; i < photoData.sizes.length; i++) {
|
||||||
|
if (biggestIndex === -1 || photoData.sizes[i].width > photoData.sizes[biggestIndex].width) {
|
||||||
|
biggestIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (biggestIndex > -1) {
|
||||||
|
// imageDelegate.imageWidth = photoData.sizes[biggestIndex].width;
|
||||||
|
// imageDelegate.imageHeight = photoData.sizes[biggestIndex].height;
|
||||||
|
image.sourceSize.width = photoData.sizes[biggestIndex].width
|
||||||
|
image.sourceSize.height = photoData.sizes[biggestIndex].height
|
||||||
|
image.fileInformation = photoData.sizes[biggestIndex].photo
|
||||||
|
|
||||||
|
console.log('loading photo', JSON.stringify(image.fileInformation))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TDLibImage {
|
||||||
|
id: image
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
source: file.isDownloadingCompleted ? file.path : ""
|
||||||
|
// enabled: true //!!file.fileId
|
||||||
|
// anchors.fill: parent
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
asynchronous: true
|
||||||
|
smooth: !(movingVertically || movingHorizontally)
|
||||||
|
|
||||||
|
// sourceSize.width: Screen.height
|
||||||
|
// visible: opacity > 0
|
||||||
|
// opacity: status === Image.Ready ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity { FadeAnimator{} }
|
||||||
|
}
|
||||||
|
// Label {
|
||||||
|
// anchors.fill: parent
|
||||||
|
// text: 'ok?' + image.enabled +' fileid:' +!!(image.file.fileId)
|
||||||
|
// + '\n - dl?' + image.file.isDownloadingActive
|
||||||
|
// + '\n completed?' + image.file.isDownloadingCompleted + ' path:'+ image.file.path
|
||||||
|
// + '\n ' + image.source
|
||||||
|
// wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||||
|
// }
|
||||||
|
// Rectangle {
|
||||||
|
// color: 'green'
|
||||||
|
// anchors.fill: image
|
||||||
|
// opacity: 0.3
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Image {
|
||||||
|
// id: image
|
||||||
|
// anchors.fill: parent
|
||||||
|
// smooth: !(movingVertically || movingHorizontally)
|
||||||
|
// sourceSize.width: Screen.height
|
||||||
|
// fillMode: Image.PreserveAspectFit
|
||||||
|
// asynchronous: true
|
||||||
|
// cache: false
|
||||||
|
|
||||||
|
// onSourceChanged: {
|
||||||
|
// zoomOut()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// opacity: status == Image.Ready ? 1 : 0
|
||||||
|
// Behavior on opacity { FadeAnimator{} }
|
||||||
|
// }
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: zoomArea.contentWidth
|
||||||
|
height: zoomArea.contentHeight
|
||||||
|
onClicked: zoomArea.clicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
running: image.file.isDownloadingActive && !delayBusyIndicator.running
|
||||||
|
size: BusyIndicatorSize.Large
|
||||||
|
anchors.centerIn: parent
|
||||||
|
parent: zoomArea
|
||||||
|
Timer {
|
||||||
|
id: delayBusyIndicator
|
||||||
|
running: image.file.isDownloadingActive
|
||||||
|
interval: 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Rectangle {
|
||||||
|
// color: 'green'
|
||||||
|
// anchors.fill: parent
|
||||||
|
// parent: zoomArea
|
||||||
|
// }
|
||||||
|
}
|
|
@ -609,7 +609,8 @@ Page {
|
||||||
Connections {
|
Connections {
|
||||||
target: chatModel
|
target: chatModel
|
||||||
onMessagesReceived: {
|
onMessagesReceived: {
|
||||||
Debug.log("[ChatPage] Messages received, view has ", chatView.count, " messages, last known message index ", modelIndex, ", own messages were read before index ", lastReadSentIndex);
|
var proxyIndex = chatProxyModel.mapRowFromSource(modelIndex, -1);
|
||||||
|
Debug.log("[ChatPage] Messages received, view has ", chatView.count, " messages, last known message index ", proxyIndex, "("+modelIndex+"), own messages were read before index ", lastReadSentIndex);
|
||||||
if (totalCount === 0) {
|
if (totalCount === 0) {
|
||||||
if (chatPage.iterativeInitialization) {
|
if (chatPage.iterativeInitialization) {
|
||||||
chatPage.iterativeInitialization = false;
|
chatPage.iterativeInitialization = false;
|
||||||
|
@ -623,9 +624,9 @@ Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
chatView.lastReadSentIndex = lastReadSentIndex;
|
chatView.lastReadSentIndex = lastReadSentIndex;
|
||||||
chatView.scrollToIndex(modelIndex);
|
chatView.scrollToIndex(proxyIndex);
|
||||||
chatPage.loading = false;
|
chatPage.loading = false;
|
||||||
if (chatOverviewItem.visible && modelIndex >= (chatView.count - 10)) {
|
if (chatOverviewItem.visible && proxyIndex >= (chatView.count - 10)) {
|
||||||
chatView.inCooldown = true;
|
chatView.inCooldown = true;
|
||||||
chatModel.triggerLoadMoreFuture();
|
chatModel.triggerLoadMoreFuture();
|
||||||
}
|
}
|
||||||
|
@ -669,10 +670,13 @@ Page {
|
||||||
chatView.lastReadSentIndex = lastReadSentIndex;
|
chatView.lastReadSentIndex = lastReadSentIndex;
|
||||||
}
|
}
|
||||||
onMessagesIncrementalUpdate: {
|
onMessagesIncrementalUpdate: {
|
||||||
Debug.log("Incremental update received. View now has ", chatView.count, " messages, view is on index ", modelIndex, ", own messages were read before index ", lastReadSentIndex);
|
var proxyIndex = chatProxyModel.mapRowFromSource(modelIndex, -1);
|
||||||
|
Debug.log("Incremental update received. View now has ", chatView.count, " messages, view is on index ", proxyIndex, "("+modelIndex+"), own messages were read before index ", lastReadSentIndex);
|
||||||
chatView.lastReadSentIndex = lastReadSentIndex;
|
chatView.lastReadSentIndex = lastReadSentIndex;
|
||||||
if (!chatPage.isInitialized) {
|
if (!chatPage.isInitialized) {
|
||||||
chatView.scrollToIndex(modelIndex);
|
if (proxyIndex > -1) {
|
||||||
|
chatView.scrollToIndex(proxyIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (chatView.height > chatView.contentHeight) {
|
if (chatView.height > chatView.contentHeight) {
|
||||||
Debug.log("[ChatPage] Chat content quite small...");
|
Debug.log("[ChatPage] Chat content quite small...");
|
||||||
|
@ -748,14 +752,26 @@ Page {
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
Debug.log("scroll position changed, message index: ", lastQueuedIndex);
|
Debug.log("scroll position changed, message index: ", lastQueuedIndex);
|
||||||
Debug.log("unread count: ", chatInformation.unread_count);
|
Debug.log("unread count: ", chatInformation.unread_count);
|
||||||
var messageToRead = chatModel.getMessage(lastQueuedIndex);
|
var modelIndex = chatProxyModel.mapRowToSource(lastQueuedIndex);
|
||||||
|
var messageToRead = chatModel.getMessage(modelIndex);
|
||||||
if (messageToRead['@type'] === "sponsoredMessage") {
|
if (messageToRead['@type'] === "sponsoredMessage") {
|
||||||
Debug.log("sponsored message to read: ", messageToRead.id);
|
Debug.log("sponsored message to read: ", messageToRead.id);
|
||||||
tdLibWrapper.viewMessage(chatInformation.id, messageToRead.message_id, false);
|
tdLibWrapper.viewMessage(chatInformation.id, messageToRead.message_id, false);
|
||||||
} else if (chatInformation.unread_count > 0 && lastQueuedIndex > -1) {
|
} else if (chatInformation.unread_count > 0 && lastQueuedIndex > -1) {
|
||||||
|
if (messageToRead) {
|
||||||
Debug.log("message to read: ", messageToRead.id);
|
Debug.log("message to read: ", messageToRead.id);
|
||||||
if (messageToRead && messageToRead.id) {
|
var messageId = messageToRead.id;
|
||||||
tdLibWrapper.viewMessage(chatInformation.id, messageToRead.id, false);
|
var type = messageToRead.content["@type"];
|
||||||
|
if (messageToRead.media_album_id !== '0') {
|
||||||
|
var albumIds = chatModel.getMessageIdsForAlbum(messageToRead.media_album_id);
|
||||||
|
if (albumIds.length > 0) {
|
||||||
|
messageId = albumIds[albumIds.length - 1];
|
||||||
|
Debug.log("message to read last album message id: ", messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (messageId) {
|
||||||
|
tdLibWrapper.viewMessage(chatInformation.id, messageId, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
lastQueuedIndex = -1
|
lastQueuedIndex = -1
|
||||||
}
|
}
|
||||||
|
@ -1223,7 +1239,6 @@ Page {
|
||||||
readonly property int messageInReplyToHeight: Theme.fontSizeExtraSmall * 2.571428571 + Theme.paddingSmall;
|
readonly property int messageInReplyToHeight: Theme.fontSizeExtraSmall * 2.571428571 + Theme.paddingSmall;
|
||||||
readonly property int webPagePreviewHeight: ( (textColumnWidth * 2 / 3) + (6 * Theme.fontSizeExtraSmall) + ( 7 * Theme.paddingSmall) )
|
readonly property int webPagePreviewHeight: ( (textColumnWidth * 2 / 3) + (6 * Theme.fontSizeExtraSmall) + ( 7 * Theme.paddingSmall) )
|
||||||
readonly property bool pageIsSelecting: chatPage.isSelecting
|
readonly property bool pageIsSelecting: chatPage.isSelecting
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleScrollPositionChanged() {
|
function handleScrollPositionChanged() {
|
||||||
|
@ -1246,6 +1261,9 @@ Page {
|
||||||
positionViewAtIndex(index, (mode === undefined) ? ListView.Contain : mode)
|
positionViewAtIndex(index, (mode === undefined) ? ListView.Contain : mode)
|
||||||
if(index === chatView.count - 1) {
|
if(index === chatView.count - 1) {
|
||||||
manuallyScrolledToBottom = true;
|
manuallyScrolledToBottom = true;
|
||||||
|
if(!chatView.atYEnd) {
|
||||||
|
chatView.positionViewAtEnd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1278,7 +1296,13 @@ Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model: chatModel
|
BoolFilterModel {
|
||||||
|
id: chatProxyModel
|
||||||
|
sourceModel: chatModel
|
||||||
|
filterRoleName: "album_entry_filter"
|
||||||
|
filterValue: false
|
||||||
|
}
|
||||||
|
model: chatProxyModel
|
||||||
header: Component {
|
header: Component {
|
||||||
Loader {
|
Loader {
|
||||||
active: !!chatPage.botInformation
|
active: !!chatPage.botInformation
|
||||||
|
@ -1311,7 +1335,8 @@ Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContentComponentHeight(contentType, content, parentWidth) {
|
function getContentComponentHeight(contentType, content, parentWidth, albumEntries) {
|
||||||
|
var unit;
|
||||||
switch(contentType) {
|
switch(contentType) {
|
||||||
case "messageAnimatedEmoji":
|
case "messageAnimatedEmoji":
|
||||||
return content.animated_emoji.sticker.height;
|
return content.animated_emoji.sticker.height;
|
||||||
|
@ -1327,6 +1352,10 @@ Page {
|
||||||
case "messageVenue":
|
case "messageVenue":
|
||||||
return parentWidth * 0.66666666; // 2 / 3;
|
return parentWidth * 0.66666666; // 2 / 3;
|
||||||
case "messagePhoto":
|
case "messagePhoto":
|
||||||
|
if(albumEntries > 0) {
|
||||||
|
unit = (parentWidth * 0.66666666)
|
||||||
|
return (albumEntries % 2 !== 0 ? unit * 0.75 : 0) + unit * albumEntries * 0.25
|
||||||
|
}
|
||||||
var biggest = content.photo.sizes[content.photo.sizes.length - 1];
|
var biggest = content.photo.sizes[content.photo.sizes.length - 1];
|
||||||
var aspectRatio = biggest.width/biggest.height;
|
var aspectRatio = biggest.width/biggest.height;
|
||||||
return Math.max(Theme.itemSizeExtraSmall, Math.min(parentWidth * 0.66666666, parentWidth / aspectRatio));
|
return Math.max(Theme.itemSizeExtraSmall, Math.min(parentWidth * 0.66666666, parentWidth / aspectRatio));
|
||||||
|
@ -1335,6 +1364,10 @@ Page {
|
||||||
case "messageSticker":
|
case "messageSticker":
|
||||||
return content.sticker.height;
|
return content.sticker.height;
|
||||||
case "messageVideo":
|
case "messageVideo":
|
||||||
|
if(albumEntries > 0) {
|
||||||
|
unit = (parentWidth * 0.66666666)
|
||||||
|
return (albumEntries % 2 !== 0 ? unit * 0.75 : 0) + unit * albumEntries * 0.25
|
||||||
|
}
|
||||||
return Functions.getVideoHeight(parentWidth, content.video);
|
return Functions.getVideoHeight(parentWidth, content.video);
|
||||||
case "messageVideoNote":
|
case "messageVideoNote":
|
||||||
return parentWidth
|
return parentWidth
|
||||||
|
@ -1390,10 +1423,11 @@ Page {
|
||||||
chatId: chatModel.chatId
|
chatId: chatModel.chatId
|
||||||
myMessage: model.display
|
myMessage: model.display
|
||||||
messageId: model.message_id
|
messageId: model.message_id
|
||||||
|
messageAlbumMessageIds: model.album_message_ids
|
||||||
messageViewCount: model.view_count
|
messageViewCount: model.view_count
|
||||||
reactions: model.reactions
|
reactions: model.reactions
|
||||||
chatReactions: availableReactions
|
chatReactions: availableReactions
|
||||||
messageIndex: model.index
|
messageIndex: chatProxyModel.mapRowToSource(model.index)
|
||||||
hasContentComponent: !!myMessage.content && chatView.delegateMessagesContent.indexOf(model.content_type) > -1
|
hasContentComponent: !!myMessage.content && chatView.delegateMessagesContent.indexOf(model.content_type) > -1
|
||||||
canReplyToMessage: chatPage.canSendMessages
|
canReplyToMessage: chatPage.canSendMessages
|
||||||
onReplyToMessage: {
|
onReplyToMessage: {
|
||||||
|
@ -1414,9 +1448,21 @@ Page {
|
||||||
id: messageListViewItemSimpleComponent
|
id: messageListViewItemSimpleComponent
|
||||||
MessageListViewItemSimple {}
|
MessageListViewItemSimple {}
|
||||||
}
|
}
|
||||||
sourceComponent: chatView.simpleDelegateMessages.indexOf(model.content_type) > -1 ? messageListViewItemSimpleComponent : messageListViewItemComponent
|
Component {
|
||||||
|
id: messageListViewItemHiddenComponent
|
||||||
|
Item {
|
||||||
|
property var myMessage: display
|
||||||
|
property bool senderIsUser: myMessage.sender_id["@type"] === "messageSenderUser"
|
||||||
|
property var userInformation: senderIsUser ? tdLibWrapper.getUserInformation(myMessage.sender_id.user_id) : null
|
||||||
|
property bool isOwnMessage: senderIsUser && chatPage.myUserId === myMessage.sender_id.user_id
|
||||||
|
height: 1
|
||||||
}
|
}
|
||||||
VerticalScrollDecorator {}
|
}
|
||||||
|
sourceComponent: chatView.simpleDelegateMessages.indexOf(model.content_type) > -1
|
||||||
|
? messageListViewItemSimpleComponent
|
||||||
|
: messageListViewItemComponent
|
||||||
|
}
|
||||||
|
VerticalScrollDecorator { flickable: chatView }
|
||||||
|
|
||||||
ViewPlaceholder {
|
ViewPlaceholder {
|
||||||
id: chatViewPlaceholder
|
id: chatViewPlaceholder
|
||||||
|
|
109
qml/pages/MediaAlbumPage.qml
Normal file
109
qml/pages/MediaAlbumPage.qml
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
// jolla-gallery/pages/FlickableImageView.qml
|
||||||
|
/*
|
||||||
|
|
||||||
|
FullscreenContentPage
|
||||||
|
- PagedView (jolla-gallery/FlickableImageView)
|
||||||
|
- delegate: Loader
|
||||||
|
- SilicaFlickable (Silica.private/ZoomableFlickable) (Sailfish.Gallery/ImageViewer)
|
||||||
|
- PinchArea
|
||||||
|
- dragDetector(?)
|
||||||
|
- image
|
||||||
|
- Item (Sailfish.Gallery/GalleryOverlay)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.6
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
import WerkWolf.Fernschreiber 1.0
|
||||||
|
import "../components"
|
||||||
|
|
||||||
|
import "../components/messageContent/mediaAlbumPage"
|
||||||
|
import "../js/twemoji.js" as Emoji
|
||||||
|
import "../js/functions.js" as Functions
|
||||||
|
|
||||||
|
Page {
|
||||||
|
// id
|
||||||
|
id: page
|
||||||
|
// property declarations
|
||||||
|
|
||||||
|
property alias index: pagedView.currentIndex
|
||||||
|
property alias overlayActive: overlay.active
|
||||||
|
property alias delegate: pagedView.delegate
|
||||||
|
property var messages: [];
|
||||||
|
// message.content.caption.text
|
||||||
|
palette.colorScheme: Theme.LightOnDark
|
||||||
|
clip: status !== PageStatus.Active || pageStack.dragInProgress
|
||||||
|
navigationStyle: PageNavigation.Vertical
|
||||||
|
backgroundColor: 'black'
|
||||||
|
allowedOrientations: Orientation.All
|
||||||
|
// signal declarations
|
||||||
|
// JavaScript functions
|
||||||
|
|
||||||
|
// object (parent) properties
|
||||||
|
// large property bindings
|
||||||
|
// child objects
|
||||||
|
// states
|
||||||
|
// transitions
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// content
|
||||||
|
PagedView {
|
||||||
|
id: pagedView
|
||||||
|
anchors.fill: parent
|
||||||
|
model: messages
|
||||||
|
delegate: Component {
|
||||||
|
Loader {
|
||||||
|
id: loader
|
||||||
|
asynchronous: true
|
||||||
|
visible: status == Loader.Ready
|
||||||
|
width: PagedView.contentWidth
|
||||||
|
height: PagedView.contentHeight
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
when: model.modelData.content['@type'] === 'messagePhoto'
|
||||||
|
PropertyChanges {
|
||||||
|
target: loader
|
||||||
|
source: "../components/messageContent/mediaAlbumPage/PhotoComponent.qml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
when: model.modelData.content['@type'] === 'messageVideo'
|
||||||
|
PropertyChanges {
|
||||||
|
target: loader
|
||||||
|
source: "../components/messageContent/mediaAlbumPage/VideoComponent.qml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// overlay
|
||||||
|
FullscreenOverlay {
|
||||||
|
id: overlay
|
||||||
|
pageCount: messages.length
|
||||||
|
currentIndex: page.index
|
||||||
|
message: messages[currentIndex]
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
153
src/boolfiltermodel.cpp
Normal file
153
src/boolfiltermodel.cpp
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "boolfiltermodel.h"
|
||||||
|
|
||||||
|
#define DEBUG_MODULE BoolFilterModel
|
||||||
|
#include "debuglog.h"
|
||||||
|
|
||||||
|
BoolFilterModel::BoolFilterModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||||
|
{
|
||||||
|
setDynamicSortFilter(true);
|
||||||
|
// setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||||
|
// setFilterFixedString(QString());
|
||||||
|
filterValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoolFilterModel::setSource(QObject *model)
|
||||||
|
{
|
||||||
|
setSourceModel(qobject_cast<QAbstractItemModel*>(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoolFilterModel::setSourceModel(QAbstractItemModel *model)
|
||||||
|
{
|
||||||
|
if (sourceModel() != model) {
|
||||||
|
LOG(model);
|
||||||
|
QSortFilterProxyModel::setSourceModel(model);
|
||||||
|
updateFilterRole();
|
||||||
|
emit sourceChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BoolFilterModel::getFilterRoleName() const
|
||||||
|
{
|
||||||
|
return filterRoleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoolFilterModel::setFilterRoleName(QString role)
|
||||||
|
{
|
||||||
|
if (filterRoleName != role) {
|
||||||
|
filterRoleName = role;
|
||||||
|
LOG(role);
|
||||||
|
updateFilterRole();
|
||||||
|
emit filterRoleNameChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BoolFilterModel::getFilterValue() const
|
||||||
|
{
|
||||||
|
return filterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoolFilterModel::setFilterValue(bool value)
|
||||||
|
{
|
||||||
|
if(value != filterValue) {
|
||||||
|
filterValue = value;
|
||||||
|
invalidateFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int BoolFilterModel::mapRowFromSource(int i, int fallbackDirection)
|
||||||
|
{
|
||||||
|
QModelIndex myIndex = mapFromSource(sourceModel()->index(i, 0));
|
||||||
|
LOG("mapping index" << i << "to source model:" << myIndex.row() << "valid?" << myIndex.isValid());
|
||||||
|
if(myIndex.isValid()) {
|
||||||
|
return myIndex.row();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fallbackDirection > 0) {
|
||||||
|
int max = sourceModel()->rowCount();
|
||||||
|
i += 1;
|
||||||
|
while (i < max) {
|
||||||
|
myIndex = mapFromSource(sourceModel()->index(i, 0));
|
||||||
|
|
||||||
|
LOG("fallback ++ " << i << "to source model:" << myIndex.row() << "valid?" << myIndex.isValid());
|
||||||
|
if(myIndex.isValid()) {
|
||||||
|
return myIndex.row();
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
} else if(fallbackDirection < 0) {
|
||||||
|
i -= 1;
|
||||||
|
while (i > -1) {
|
||||||
|
myIndex = mapFromSource(sourceModel()->index(i, 0));
|
||||||
|
LOG("fallback -- " << i << "to source model:" << myIndex.row() << "valid?" << myIndex.isValid());
|
||||||
|
if(myIndex.isValid()) {
|
||||||
|
return myIndex.row();
|
||||||
|
}
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return myIndex.row(); // may still be -1
|
||||||
|
}
|
||||||
|
|
||||||
|
int BoolFilterModel::mapRowToSource(int i)
|
||||||
|
{
|
||||||
|
QModelIndex sourceIndex = mapToSource(index(i, 0));
|
||||||
|
return sourceIndex.row();
|
||||||
|
}
|
||||||
|
bool BoolFilterModel::filterAcceptsRow(int sourceRow,
|
||||||
|
const QModelIndex &sourceParent) const
|
||||||
|
{
|
||||||
|
// sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(); //.toString().contains( /*string for column 0*/ ))
|
||||||
|
// LOG("Filter Role " << filterRole());
|
||||||
|
// QModelIndex index = this->sourceModel()->index(sourceRow,1,sourceParent);
|
||||||
|
// sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(filterRole()).toBool();
|
||||||
|
// LOG("Filter index DATA"<< sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(filterRole())); //<< index << index.isValid());
|
||||||
|
// LOG("Filter parent " << sourceParent << sourceParent.isValid());
|
||||||
|
// LOG("Filter Model Value" << sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(filterRole()).toBool());
|
||||||
|
// LOG("Filter Model filterValue" << filterValue);
|
||||||
|
// LOG("Filter Model result" << (sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(filterRole()).toBool() == filterValue));
|
||||||
|
// LOG("Filter Model MESSAGE" << sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data());
|
||||||
|
return sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(filterRole()).toBool() == filterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BoolFilterModel::findRole(QAbstractItemModel *model, QString role)
|
||||||
|
{
|
||||||
|
if (model && !role.isEmpty()) {
|
||||||
|
const QByteArray roleName(role.toUtf8());
|
||||||
|
const QHash<int,QByteArray> roleMap(model->roleNames());
|
||||||
|
const QList<int> roles(roleMap.keys());
|
||||||
|
const int n = roles.count();
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
const QByteArray name(roleMap.value(roles.at(i)));
|
||||||
|
if (name == roleName) {
|
||||||
|
LOG(role << roles.at(i));
|
||||||
|
return roles.at(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG("Unknown role" << role);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoolFilterModel::updateFilterRole()
|
||||||
|
{
|
||||||
|
const int role = findRole(sourceModel(), filterRoleName);
|
||||||
|
setFilterRole((role >= 0) ? role : Qt::DisplayRole);
|
||||||
|
}
|
63
src/boolfiltermodel.h
Normal file
63
src/boolfiltermodel.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BOOLFILTERMODEL_H
|
||||||
|
#define BOOLFILTERMODEL_H
|
||||||
|
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
class BoolFilterModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QString filterRoleName READ getFilterRoleName WRITE setFilterRoleName NOTIFY filterRoleNameChanged)
|
||||||
|
Q_PROPERTY(bool filterValue READ getFilterValue WRITE setFilterValue NOTIFY filterValueChanged)
|
||||||
|
Q_PROPERTY(QObject* sourceModel READ sourceModel WRITE setSource NOTIFY sourceChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
BoolFilterModel(QObject *parent = Q_NULLPTR);
|
||||||
|
|
||||||
|
void setSource(QObject* model);
|
||||||
|
void setSourceModel(QAbstractItemModel *model) Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
|
|
||||||
|
QString getFilterRoleName() const;
|
||||||
|
void setFilterRoleName(QString role);
|
||||||
|
|
||||||
|
bool getFilterValue() const;
|
||||||
|
void setFilterValue(bool value);
|
||||||
|
Q_INVOKABLE int mapRowFromSource(int i, int fallbackDirection);
|
||||||
|
Q_INVOKABLE int mapRowToSource(int i);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void sourceChanged();
|
||||||
|
void filterRoleNameChanged();
|
||||||
|
void filterValueChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void updateFilterRole();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static int findRole(QAbstractItemModel *model, QString role);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString filterRoleName;
|
||||||
|
bool filterValue;
|
||||||
|
protected:
|
||||||
|
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BOOLFILTERMODEL_H
|
|
@ -30,6 +30,7 @@ namespace {
|
||||||
const QString ID("id");
|
const QString ID("id");
|
||||||
const QString CONTENT("content");
|
const QString CONTENT("content");
|
||||||
const QString CHAT_ID("chat_id");
|
const QString CHAT_ID("chat_id");
|
||||||
|
const QString DATE("date");
|
||||||
const QString PHOTO("photo");
|
const QString PHOTO("photo");
|
||||||
const QString SMALL("small");
|
const QString SMALL("small");
|
||||||
const QString UNREAD_COUNT("unread_count");
|
const QString UNREAD_COUNT("unread_count");
|
||||||
|
@ -48,6 +49,7 @@ namespace {
|
||||||
// "view_count": 47
|
// "view_count": 47
|
||||||
// }
|
// }
|
||||||
const QString TYPE_MESSAGE_INTERACTION_INFO("messageInteractionInfo");
|
const QString TYPE_MESSAGE_INTERACTION_INFO("messageInteractionInfo");
|
||||||
|
const QString MEDIA_ALBUM_ID("media_album_id");
|
||||||
const QString INTERACTION_INFO("interaction_info");
|
const QString INTERACTION_INFO("interaction_info");
|
||||||
const QString VIEW_COUNT("view_count");
|
const QString VIEW_COUNT("view_count");
|
||||||
const QString REACTIONS("reactions");
|
const QString REACTIONS("reactions");
|
||||||
|
@ -63,7 +65,9 @@ public:
|
||||||
RoleMessageId,
|
RoleMessageId,
|
||||||
RoleMessageContentType,
|
RoleMessageContentType,
|
||||||
RoleMessageViewCount,
|
RoleMessageViewCount,
|
||||||
RoleMessageReactions
|
RoleMessageReactions,
|
||||||
|
RoleMessageAlbumEntryFilter,
|
||||||
|
RoleMessageAlbumMessageIds,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum RoleFlag {
|
enum RoleFlag {
|
||||||
|
@ -71,7 +75,9 @@ public:
|
||||||
RoleFlagMessageId = 0x02,
|
RoleFlagMessageId = 0x02,
|
||||||
RoleFlagMessageContentType = 0x04,
|
RoleFlagMessageContentType = 0x04,
|
||||||
RoleFlagMessageViewCount = 0x08,
|
RoleFlagMessageViewCount = 0x08,
|
||||||
RoleFlagMessageReactions = 0x16
|
RoleFlagMessageReactions = 0x16,
|
||||||
|
RoleFlagMessageAlbumEntryFilter = 0x32,
|
||||||
|
RoleFlagMessageAlbumMessageIds = 0x64
|
||||||
};
|
};
|
||||||
|
|
||||||
MessageData(const QVariantMap &data, qlonglong msgid);
|
MessageData(const QVariantMap &data, qlonglong msgid);
|
||||||
|
@ -86,12 +92,16 @@ public:
|
||||||
uint updateViewCount(const QVariantMap &interactionInfo);
|
uint updateViewCount(const QVariantMap &interactionInfo);
|
||||||
uint updateInteractionInfo(const QVariantMap &interactionInfo);
|
uint updateInteractionInfo(const QVariantMap &interactionInfo);
|
||||||
uint updateReactions(const QVariantMap &interactionInfo);
|
uint updateReactions(const QVariantMap &interactionInfo);
|
||||||
|
uint updateAlbumEntryFilter(const bool isAlbumChild);
|
||||||
|
uint updateAlbumEntryMessageIds(const QVariantList &newAlbumMessageIds);
|
||||||
|
|
||||||
QVector<int> diff(const MessageData *message) const;
|
QVector<int> diff(const MessageData *message) const;
|
||||||
QVector<int> setMessageData(const QVariantMap &data);
|
QVector<int> setMessageData(const QVariantMap &data);
|
||||||
QVector<int> setContent(const QVariantMap &content);
|
QVector<int> setContent(const QVariantMap &content);
|
||||||
QVector<int> setReplyMarkup(const QVariantMap &replyMarkup);
|
QVector<int> setReplyMarkup(const QVariantMap &replyMarkup);
|
||||||
QVector<int> setInteractionInfo(const QVariantMap &interactionInfo);
|
QVector<int> setInteractionInfo(const QVariantMap &interactionInfo);
|
||||||
|
QVector<int> setAlbumEntryFilter(bool isAlbumChild);
|
||||||
|
QVector<int> setAlbumEntryMessageIds(const QVariantList &newAlbumMessageIds);
|
||||||
|
|
||||||
int senderUserId() const;
|
int senderUserId() const;
|
||||||
qlonglong senderChatId() const;
|
qlonglong senderChatId() const;
|
||||||
|
@ -104,6 +114,8 @@ public:
|
||||||
QString messageContentType;
|
QString messageContentType;
|
||||||
int viewCount;
|
int viewCount;
|
||||||
QVariantList reactions;
|
QVariantList reactions;
|
||||||
|
bool albumEntryFilter;
|
||||||
|
QVariantList albumMessageIds;
|
||||||
};
|
};
|
||||||
|
|
||||||
ChatModel::MessageData::MessageData(const QVariantMap &data, qlonglong msgid) :
|
ChatModel::MessageData::MessageData(const QVariantMap &data, qlonglong msgid) :
|
||||||
|
@ -112,7 +124,9 @@ ChatModel::MessageData::MessageData(const QVariantMap &data, qlonglong msgid) :
|
||||||
messageType(data.value(_TYPE).toString()),
|
messageType(data.value(_TYPE).toString()),
|
||||||
messageContentType(data.value(CONTENT).toMap().value(_TYPE).toString()),
|
messageContentType(data.value(CONTENT).toMap().value(_TYPE).toString()),
|
||||||
viewCount(data.value(INTERACTION_INFO).toMap().value(VIEW_COUNT).toInt()),
|
viewCount(data.value(INTERACTION_INFO).toMap().value(VIEW_COUNT).toInt()),
|
||||||
reactions(data.value(INTERACTION_INFO).toMap().value(REACTIONS).toList())
|
reactions(data.value(INTERACTION_INFO).toMap().value(REACTIONS).toList()),
|
||||||
|
albumEntryFilter(false),
|
||||||
|
albumMessageIds(QVariantList())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +148,12 @@ QVector<int> ChatModel::MessageData::flagsToRoles(uint flags)
|
||||||
if (flags & RoleFlagMessageReactions) {
|
if (flags & RoleFlagMessageReactions) {
|
||||||
roles.append(RoleMessageReactions);
|
roles.append(RoleMessageReactions);
|
||||||
}
|
}
|
||||||
|
if (flags & RoleFlagMessageAlbumEntryFilter) {
|
||||||
|
roles.append(RoleMessageAlbumEntryFilter);
|
||||||
|
}
|
||||||
|
if (flags & RoleFlagMessageAlbumMessageIds) {
|
||||||
|
roles.append(RoleMessageAlbumMessageIds);
|
||||||
|
}
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,6 +189,12 @@ QVector<int> ChatModel::MessageData::diff(const MessageData *message) const
|
||||||
if (message->reactions != reactions) {
|
if (message->reactions != reactions) {
|
||||||
roles.append(RoleMessageReactions);
|
roles.append(RoleMessageReactions);
|
||||||
}
|
}
|
||||||
|
if (message->albumEntryFilter != albumEntryFilter) {
|
||||||
|
roles.append(RoleMessageAlbumEntryFilter);
|
||||||
|
}
|
||||||
|
if (message->albumMessageIds != albumMessageIds) {
|
||||||
|
roles.append(RoleMessageAlbumMessageIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
@ -237,6 +263,37 @@ uint ChatModel::MessageData::updateReactions(const QVariantMap &interactionInfo)
|
||||||
return (reactions == oldReactions) ? 0 : RoleFlagMessageReactions;
|
return (reactions == oldReactions) ? 0 : RoleFlagMessageReactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint ChatModel::MessageData::updateAlbumEntryFilter(const bool isAlbumChild)
|
||||||
|
{
|
||||||
|
LOG("Updating album filter... for id " << messageId << " value:" << isAlbumChild << "previously" << albumEntryFilter);
|
||||||
|
const bool oldAlbumFiltered = albumEntryFilter;
|
||||||
|
albumEntryFilter = isAlbumChild;
|
||||||
|
return (isAlbumChild == oldAlbumFiltered) ? 0 : RoleFlagMessageAlbumEntryFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QVector<int> ChatModel::MessageData::setAlbumEntryFilter(bool isAlbumChild)
|
||||||
|
{
|
||||||
|
LOG("setAlbumEntryFilter");
|
||||||
|
return flagsToRoles(updateAlbumEntryFilter(isAlbumChild));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ChatModel::MessageData::updateAlbumEntryMessageIds(const QVariantList &newAlbumMessageIds)
|
||||||
|
{
|
||||||
|
LOG("Updating albumMessageIds... id" << messageId);
|
||||||
|
LOG(" Updating albumMessageIds..." << newAlbumMessageIds << "previously" << albumMessageIds << "same?" << (newAlbumMessageIds == albumMessageIds));
|
||||||
|
const QVariantList oldAlbumMessageIds = albumMessageIds;
|
||||||
|
albumMessageIds = newAlbumMessageIds;
|
||||||
|
|
||||||
|
LOG(" Updating albumMessageIds... same again?" << (newAlbumMessageIds == oldAlbumMessageIds));
|
||||||
|
return (newAlbumMessageIds == oldAlbumMessageIds) ? 0 : RoleFlagMessageAlbumMessageIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<int> ChatModel::MessageData::setAlbumEntryMessageIds(const QVariantList &newAlbumMessageIds)
|
||||||
|
{
|
||||||
|
return flagsToRoles(updateAlbumEntryMessageIds(newAlbumMessageIds));
|
||||||
|
}
|
||||||
|
|
||||||
QVector<int> ChatModel::MessageData::setInteractionInfo(const QVariantMap &info)
|
QVector<int> ChatModel::MessageData::setInteractionInfo(const QVariantMap &info)
|
||||||
{
|
{
|
||||||
return flagsToRoles(updateInteractionInfo(info));
|
return flagsToRoles(updateInteractionInfo(info));
|
||||||
|
@ -295,6 +352,8 @@ QHash<int,QByteArray> ChatModel::roleNames() const
|
||||||
roles.insert(MessageData::RoleMessageContentType, "content_type");
|
roles.insert(MessageData::RoleMessageContentType, "content_type");
|
||||||
roles.insert(MessageData::RoleMessageViewCount, "view_count");
|
roles.insert(MessageData::RoleMessageViewCount, "view_count");
|
||||||
roles.insert(MessageData::RoleMessageReactions, "reactions");
|
roles.insert(MessageData::RoleMessageReactions, "reactions");
|
||||||
|
roles.insert(MessageData::RoleMessageAlbumEntryFilter, "album_entry_filter");
|
||||||
|
roles.insert(MessageData::RoleMessageAlbumMessageIds, "album_message_ids");
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,6 +373,8 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const
|
||||||
case MessageData::RoleMessageContentType: return message->messageContentType;
|
case MessageData::RoleMessageContentType: return message->messageContentType;
|
||||||
case MessageData::RoleMessageViewCount: return message->viewCount;
|
case MessageData::RoleMessageViewCount: return message->viewCount;
|
||||||
case MessageData::RoleMessageReactions: return message->reactions;
|
case MessageData::RoleMessageReactions: return message->reactions;
|
||||||
|
case MessageData::RoleMessageAlbumEntryFilter: return message->albumEntryFilter;
|
||||||
|
case MessageData::RoleMessageAlbumMessageIds: return message->albumMessageIds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
@ -331,6 +392,7 @@ void ChatModel::clear(bool contentOnly)
|
||||||
qDeleteAll(messages);
|
qDeleteAll(messages);
|
||||||
messages.clear();
|
messages.clear();
|
||||||
messageIndexMap.clear();
|
messageIndexMap.clear();
|
||||||
|
albumMessageMap.clear();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,6 +418,7 @@ void ChatModel::initialize(const QVariantMap &chatInformation)
|
||||||
this->chatId = chatId;
|
this->chatId = chatId;
|
||||||
this->messages.clear();
|
this->messages.clear();
|
||||||
this->messageIndexMap.clear();
|
this->messageIndexMap.clear();
|
||||||
|
this->albumMessageMap.clear();
|
||||||
this->searchQuery.clear();
|
this->searchQuery.clear();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
emit chatIdChanged();
|
emit chatIdChanged();
|
||||||
|
@ -420,6 +483,36 @@ int ChatModel::getMessageIndex(qlonglong messageId)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantList ChatModel::getMessageIdsForAlbum(qlonglong albumId)
|
||||||
|
{
|
||||||
|
QVariantList foundMessages;
|
||||||
|
if(albumMessageMap.contains(albumId)) { // there should be only one in here
|
||||||
|
QHash< qlonglong, QVariantList >::iterator i = albumMessageMap.find(albumId);
|
||||||
|
return i.value();
|
||||||
|
}
|
||||||
|
return foundMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList ChatModel::getMessagesForAlbum(qlonglong albumId, int startAt)
|
||||||
|
{
|
||||||
|
LOG("getMessagesForAlbumId" << albumId);
|
||||||
|
QVariantList messageIds = getMessageIdsForAlbum(albumId);
|
||||||
|
int count = messageIds.size();
|
||||||
|
if ( count == 0) {
|
||||||
|
return messageIds;
|
||||||
|
}
|
||||||
|
QVariantList foundMessages;
|
||||||
|
for (int messageNum = startAt; messageNum < count; ++messageNum) {
|
||||||
|
const int position = messageIndexMap.value(messageIds.at(messageNum).toLongLong(), -1);
|
||||||
|
if(position >= 0 && position < messages.size()) {
|
||||||
|
foundMessages.append(messages.at(position)->messageData);
|
||||||
|
} else {
|
||||||
|
LOG("Not found in messages: #"<< messageNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundMessages;
|
||||||
|
}
|
||||||
|
|
||||||
int ChatModel::getLastReadMessageIndex()
|
int ChatModel::getLastReadMessageIndex()
|
||||||
{
|
{
|
||||||
LOG("Obtaining last read message index");
|
LOG("Obtaining last read message index");
|
||||||
|
@ -477,7 +570,8 @@ void ChatModel::handleMessagesReceived(const QVariantList &messages, int totalCo
|
||||||
const qlonglong messageId = messageData.value(ID).toLongLong();
|
const qlonglong messageId = messageData.value(ID).toLongLong();
|
||||||
if (messageId && messageData.value(CHAT_ID).toLongLong() == chatId && !messageIndexMap.contains(messageId)) {
|
if (messageId && messageData.value(CHAT_ID).toLongLong() == chatId && !messageIndexMap.contains(messageId)) {
|
||||||
LOG("New message will be added:" << messageId);
|
LOG("New message will be added:" << messageId);
|
||||||
messagesToBeAdded.append(new MessageData(messageData, messageId));
|
MessageData* message = new MessageData(messageData, messageId);
|
||||||
|
messagesToBeAdded.append(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,6 +579,7 @@ void ChatModel::handleMessagesReceived(const QVariantList &messages, int totalCo
|
||||||
|
|
||||||
if (!messagesToBeAdded.isEmpty()) {
|
if (!messagesToBeAdded.isEmpty()) {
|
||||||
insertMessages(messagesToBeAdded);
|
insertMessages(messagesToBeAdded);
|
||||||
|
setMessagesAlbum(messagesToBeAdded);
|
||||||
}
|
}
|
||||||
|
|
||||||
// First call only returns a few messages, we need to get a little more than that...
|
// First call only returns a few messages, we need to get a little more than that...
|
||||||
|
@ -540,6 +635,7 @@ void ChatModel::handleNewMessageReceived(qlonglong chatId, const QVariantMap &me
|
||||||
QList<MessageData*> messagesToBeAdded;
|
QList<MessageData*> messagesToBeAdded;
|
||||||
messagesToBeAdded.append(new MessageData(message, messageId));
|
messagesToBeAdded.append(new MessageData(message, messageId));
|
||||||
insertMessages(messagesToBeAdded);
|
insertMessages(messagesToBeAdded);
|
||||||
|
setMessagesAlbum(messagesToBeAdded);
|
||||||
emit newMessageReceived(message);
|
emit newMessageReceived(message);
|
||||||
} else {
|
} else {
|
||||||
LOG("New message in this chat, but not relevant as less recent messages need to be loaded first!");
|
LOG("New message in this chat, but not relevant as less recent messages need to be loaded first!");
|
||||||
|
@ -591,6 +687,7 @@ void ChatModel::handleMessageSendSucceeded(qlonglong messageId, qlonglong oldMes
|
||||||
messages.replace(pos, newMessage);
|
messages.replace(pos, newMessage);
|
||||||
messageIndexMap.remove(oldMessageId);
|
messageIndexMap.remove(oldMessageId);
|
||||||
messageIndexMap.insert(messageId, pos);
|
messageIndexMap.insert(messageId, pos);
|
||||||
|
// TODO when we support sending album messages, handle ID change in albumMessageMap
|
||||||
const QVector<int> changedRoles(newMessage->diff(oldMessage));
|
const QVector<int> changedRoles(newMessage->diff(oldMessage));
|
||||||
delete oldMessage;
|
delete oldMessage;
|
||||||
LOG("Message was replaced at index" << pos);
|
LOG("Message was replaced at index" << pos);
|
||||||
|
@ -635,7 +732,8 @@ void ChatModel::handleMessageContentUpdated(qlonglong chatId, qlonglong messageI
|
||||||
LOG("We know the message that was updated" << messageId);
|
LOG("We know the message that was updated" << messageId);
|
||||||
const int pos = messageIndexMap.value(messageId, -1);
|
const int pos = messageIndexMap.value(messageId, -1);
|
||||||
if (pos >= 0) {
|
if (pos >= 0) {
|
||||||
const QVector<int> changedRoles(messages.at(pos)->setContent(newContent));
|
MessageData* messageData = messages.at(pos);
|
||||||
|
const QVector<int> changedRoles(messageData->setContent(newContent));
|
||||||
LOG("Message was updated at index" << pos);
|
LOG("Message was updated at index" << pos);
|
||||||
const QModelIndex messageIndex(index(pos));
|
const QModelIndex messageIndex(index(pos));
|
||||||
emit dataChanged(messageIndex, messageIndex, changedRoles);
|
emit dataChanged(messageIndex, messageIndex, changedRoles);
|
||||||
|
@ -664,7 +762,8 @@ void ChatModel::handleMessageEditedUpdated(qlonglong chatId, qlonglong messageId
|
||||||
LOG("We know the message that was updated" << messageId);
|
LOG("We know the message that was updated" << messageId);
|
||||||
const int pos = messageIndexMap.value(messageId, -1);
|
const int pos = messageIndexMap.value(messageId, -1);
|
||||||
if (pos >= 0) {
|
if (pos >= 0) {
|
||||||
const QVector<int> changedRoles(messages.at(pos)->setReplyMarkup(replyMarkup));
|
MessageData* messageData = messages.at(pos);
|
||||||
|
const QVector<int> changedRoles(messageData->setReplyMarkup(replyMarkup));
|
||||||
LOG("Message was edited at index" << pos);
|
LOG("Message was edited at index" << pos);
|
||||||
const QModelIndex messageIndex(index(pos));
|
const QModelIndex messageIndex(index(pos));
|
||||||
emit dataChanged(messageIndex, messageIndex, changedRoles);
|
emit dataChanged(messageIndex, messageIndex, changedRoles);
|
||||||
|
@ -709,18 +808,31 @@ void ChatModel::handleMessagesDeleted(qlonglong chatId, const QList<qlonglong> &
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ChatModel::removeRange(int firstDeleted, int lastDeleted)
|
void ChatModel::removeRange(int firstDeleted, int lastDeleted)
|
||||||
{
|
{
|
||||||
if (firstDeleted >= 0 && firstDeleted <= lastDeleted) {
|
if (firstDeleted >= 0 && firstDeleted <= lastDeleted) {
|
||||||
LOG("Removing range" << firstDeleted << "..." << lastDeleted << "| current messages size" << messages.size());
|
LOG("Removing range" << firstDeleted << "..." << lastDeleted << "| current messages size" << messages.size());
|
||||||
beginRemoveRows(QModelIndex(), firstDeleted, lastDeleted);
|
beginRemoveRows(QModelIndex(), firstDeleted, lastDeleted);
|
||||||
|
QList<qlonglong> rescanAlbumIds;
|
||||||
for (int i = firstDeleted; i <= lastDeleted; i++) {
|
for (int i = firstDeleted; i <= lastDeleted; i++) {
|
||||||
MessageData *message = messages.at(i);
|
MessageData *message = messages.at(i);
|
||||||
messageIndexMap.remove(message->messageId);
|
messageIndexMap.remove(message->messageId);
|
||||||
|
|
||||||
|
qlonglong albumId = message->messageData.value(MEDIA_ALBUM_ID).toLongLong();
|
||||||
|
if(albumId != 0 && albumMessageMap.contains(albumId)) {
|
||||||
|
rescanAlbumIds.append(albumId);
|
||||||
|
}
|
||||||
delete message;
|
delete message;
|
||||||
}
|
}
|
||||||
messages.erase(messages.begin() + firstDeleted, messages.begin() + (lastDeleted + 1));
|
messages.erase(messages.begin() + firstDeleted, messages.begin() + (lastDeleted + 1));
|
||||||
|
// rebuild following messageIndexMap
|
||||||
|
for(int i = firstDeleted; i < messages.size(); ++i) {
|
||||||
|
messageIndexMap.insert(messages.at(i)->messageId, i);
|
||||||
|
}
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
|
|
||||||
|
updateAlbumMessages(rescanAlbumIds, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -757,7 +869,7 @@ void ChatModel::appendMessages(const QList<MessageData*> newMessages)
|
||||||
beginInsertRows(QModelIndex(), oldSize, oldSize + count - 1);
|
beginInsertRows(QModelIndex(), oldSize, oldSize + count - 1);
|
||||||
messages.append(newMessages);
|
messages.append(newMessages);
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
// Appens new indeces to the map
|
// Append new indices to the map
|
||||||
messageIndexMap.insert(newMessages.at(i)->messageId, oldSize + i);
|
messageIndexMap.insert(newMessages.at(i)->messageId, oldSize + i);
|
||||||
}
|
}
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
|
@ -785,6 +897,90 @@ void ChatModel::prependMessages(const QList<MessageData*> newMessages)
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatModel::updateAlbumMessages(qlonglong albumId, bool checkDeleted)
|
||||||
|
{
|
||||||
|
if(albumMessageMap.contains(albumId)) {
|
||||||
|
const QVariantList empty;
|
||||||
|
QHash< qlonglong, QVariantList >::iterator album = albumMessageMap.find(albumId);
|
||||||
|
QVariantList messageIds = album.value();
|
||||||
|
std::sort(messageIds.begin(), messageIds.end());
|
||||||
|
int count;
|
||||||
|
// first: clear deleted messageIds:
|
||||||
|
if(checkDeleted) {
|
||||||
|
QVariantList::iterator it = messageIds.begin();
|
||||||
|
while (it != messageIds.end()) {
|
||||||
|
if (!messageIndexMap.contains(it->toLongLong())) {
|
||||||
|
it = messageIds.erase(it);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// second: remaining ones still exist
|
||||||
|
count = messageIds.size();
|
||||||
|
if(count == 0) {
|
||||||
|
albumMessageMap.remove(albumId);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
const int position = messageIndexMap.value(messageIds.at(i).toLongLong(), -1);
|
||||||
|
if(position > -1) {
|
||||||
|
// set list for first entry, empty for all others
|
||||||
|
QVector<int> changedRolesFilter;
|
||||||
|
QVector<int> changedRolesIds;
|
||||||
|
|
||||||
|
QModelIndex messageIndex(index(position));
|
||||||
|
if(i == 0) {
|
||||||
|
changedRolesFilter = messages.at(position)->setAlbumEntryFilter(false);
|
||||||
|
changedRolesIds = messages.at(position)->setAlbumEntryMessageIds(messageIds);
|
||||||
|
} else {
|
||||||
|
changedRolesFilter = messages.at(position)->setAlbumEntryFilter(true);
|
||||||
|
changedRolesIds = messages.at(position)->setAlbumEntryMessageIds(empty);
|
||||||
|
}
|
||||||
|
emit dataChanged(messageIndex, messageIndex, changedRolesIds);
|
||||||
|
emit dataChanged(messageIndex, messageIndex, changedRolesFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
albumMessageMap.insert(albumId, messageIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatModel::updateAlbumMessages(QList<qlonglong> albumIds, bool checkDeleted)
|
||||||
|
{
|
||||||
|
const int albumsCount = albumIds.size();
|
||||||
|
for (int i = 0; i < albumsCount; i++) {
|
||||||
|
updateAlbumMessages(albumIds.at(i), checkDeleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatModel::setMessagesAlbum(const QList<MessageData *> newMessages)
|
||||||
|
{
|
||||||
|
const int count = newMessages.size();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
setMessagesAlbum(newMessages.at(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatModel::setMessagesAlbum(MessageData *message)
|
||||||
|
{
|
||||||
|
qlonglong albumId = message->messageData.value(MEDIA_ALBUM_ID).toLongLong();
|
||||||
|
if (albumId > 0 && (message->messageContentType != "messagePhoto" || message->messageContentType != "messageVideo")) {
|
||||||
|
qlonglong messageId = message->messageId;
|
||||||
|
|
||||||
|
if(albumMessageMap.contains(albumId)) {
|
||||||
|
// find message id within album:
|
||||||
|
QHash< qlonglong, QVariantList >::iterator i = albumMessageMap.find(albumId);
|
||||||
|
if(!i.value().contains(messageId)) {
|
||||||
|
i.value().append(messageId);
|
||||||
|
}
|
||||||
|
} else { // new album id
|
||||||
|
albumMessageMap.insert(albumId, QVariantList() << messageId);
|
||||||
|
}
|
||||||
|
updateAlbumMessages(albumId, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QVariantMap ChatModel::enhanceMessage(const QVariantMap &message)
|
QVariantMap ChatModel::enhanceMessage(const QVariantMap &message)
|
||||||
{
|
{
|
||||||
QVariantMap enhancedMessage = message;
|
QVariantMap enhancedMessage = message;
|
||||||
|
|
|
@ -44,6 +44,8 @@ public:
|
||||||
Q_INVOKABLE void triggerLoadMoreFuture();
|
Q_INVOKABLE void triggerLoadMoreFuture();
|
||||||
Q_INVOKABLE QVariantMap getChatInformation();
|
Q_INVOKABLE QVariantMap getChatInformation();
|
||||||
Q_INVOKABLE QVariantMap getMessage(int index);
|
Q_INVOKABLE QVariantMap getMessage(int index);
|
||||||
|
Q_INVOKABLE QVariantList getMessageIdsForAlbum(qlonglong albumId);
|
||||||
|
Q_INVOKABLE QVariantList getMessagesForAlbum(qlonglong albumId, int startAt);
|
||||||
Q_INVOKABLE int getLastReadMessageIndex();
|
Q_INVOKABLE int getLastReadMessageIndex();
|
||||||
Q_INVOKABLE void setSearchQuery(const QString newSearchQuery);
|
Q_INVOKABLE void setSearchQuery(const QString newSearchQuery);
|
||||||
|
|
||||||
|
@ -85,6 +87,10 @@ private:
|
||||||
void insertMessages(const QList<MessageData*> newMessages);
|
void insertMessages(const QList<MessageData*> newMessages);
|
||||||
void appendMessages(const QList<MessageData*> newMessages);
|
void appendMessages(const QList<MessageData*> newMessages);
|
||||||
void prependMessages(const QList<MessageData*> newMessages);
|
void prependMessages(const QList<MessageData*> newMessages);
|
||||||
|
void updateAlbumMessages(qlonglong albumId, bool checkDeleted);
|
||||||
|
void updateAlbumMessages(QList<qlonglong> albumIds, bool checkDeleted);
|
||||||
|
void setMessagesAlbum(const QList<MessageData*> newMessages);
|
||||||
|
void setMessagesAlbum(MessageData *message);
|
||||||
QVariantMap enhanceMessage(const QVariantMap &message);
|
QVariantMap enhanceMessage(const QVariantMap &message);
|
||||||
int calculateLastKnownMessageId();
|
int calculateLastKnownMessageId();
|
||||||
int calculateLastReadSentMessageId();
|
int calculateLastReadSentMessageId();
|
||||||
|
@ -95,6 +101,7 @@ private:
|
||||||
TDLibWrapper *tdLibWrapper;
|
TDLibWrapper *tdLibWrapper;
|
||||||
QList<MessageData*> messages;
|
QList<MessageData*> messages;
|
||||||
QHash<qlonglong,int> messageIndexMap;
|
QHash<qlonglong,int> messageIndexMap;
|
||||||
|
QHash<qlonglong, QVariantList> albumMessageMap;
|
||||||
QVariantMap chatInformation;
|
QVariantMap chatInformation;
|
||||||
qlonglong chatId;
|
qlonglong chatId;
|
||||||
bool inReload;
|
bool inReload;
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
#include "processlauncher.h"
|
#include "processlauncher.h"
|
||||||
#include "stickermanager.h"
|
#include "stickermanager.h"
|
||||||
#include "textfiltermodel.h"
|
#include "textfiltermodel.h"
|
||||||
|
#include "boolfiltermodel.h"
|
||||||
#include "tgsplugin.h"
|
#include "tgsplugin.h"
|
||||||
#include "fernschreiberutils.h"
|
#include "fernschreiberutils.h"
|
||||||
#include "knownusersmodel.h"
|
#include "knownusersmodel.h"
|
||||||
|
@ -130,6 +131,7 @@ int main(int argc, char *argv[])
|
||||||
qmlRegisterType<TDLibFile>(uri, 1, 0, "TDLibFile");
|
qmlRegisterType<TDLibFile>(uri, 1, 0, "TDLibFile");
|
||||||
qmlRegisterType<NamedAction>(uri, 1, 0, "NamedAction");
|
qmlRegisterType<NamedAction>(uri, 1, 0, "NamedAction");
|
||||||
qmlRegisterType<TextFilterModel>(uri, 1, 0, "TextFilterModel");
|
qmlRegisterType<TextFilterModel>(uri, 1, 0, "TextFilterModel");
|
||||||
|
qmlRegisterType<BoolFilterModel>(uri, 1, 0, "BoolFilterModel");
|
||||||
qmlRegisterType<ChatPermissionFilterModel>(uri, 1, 0, "ChatPermissionFilterModel");
|
qmlRegisterType<ChatPermissionFilterModel>(uri, 1, 0, "ChatPermissionFilterModel");
|
||||||
qmlRegisterSingletonType<DebugLogJS>(uri, 1, 0, "DebugLog", DebugLogJS::createSingleton);
|
qmlRegisterSingletonType<DebugLogJS>(uri, 1, 0, "DebugLog", DebugLogJS::createSingleton);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue