2020-11-05 16:05:33 +03:00
/ *
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 "../js/twemoji.js" as Emoji
import "../js/functions.js" as Functions
2020-11-20 23:58:26 +03:00
import "../js/debug.js" as Debug
2020-11-05 16:05:33 +03:00
ListItem {
id: messageListItem
contentHeight: messageBackground . height + Theme . paddingMedium
2020-12-06 07:38:03 +03:00
property var chatId
property var messageId
property var myMessage
2020-12-15 21:15:12 +03:00
property bool canReplyToMessage
2020-12-25 15:50:13 +03:00
readonly property bool isAnonymous: myMessage . sender [ "@type" ] === "messageSenderChat"
2020-12-07 23:43:09 +03:00
readonly property var userInformation: tdLibWrapper . getUserInformation ( myMessage . sender . user_id )
2020-11-07 22:58:23 +03:00
property QtObject precalculatedValues: ListView . view . precalculatedValues
2020-11-08 22:37:17 +03:00
readonly property color textColor: isOwnMessage ? Theme.highlightColor : Theme . primaryColor
readonly property int textAlign: isOwnMessage ? Text.AlignRight : Text . AlignLeft
readonly property Page page: precalculatedValues . page
2020-11-15 01:50:12 +03:00
readonly property bool isSelected: messageListItem . precalculatedValues . pageIsSelecting && page . selectedMessages . some ( function ( existingMessage ) {
2020-12-06 07:38:03 +03:00
return existingMessage . id === messageId
2020-11-15 01:50:12 +03:00
} ) ;
2020-12-07 23:43:09 +03:00
readonly property bool isOwnMessage: page . myUserId === myMessage . sender . user_id
2020-12-06 07:38:03 +03:00
property string extraContentComponentName
2020-11-07 22:58:23 +03:00
2020-12-04 22:29:31 +03:00
highlighted: ( down || isSelected ) && ! menuOpen
2020-11-15 01:50:12 +03:00
openMenuOnPressAndHold: ! messageListItem . precalculatedValues . pageIsSelecting
2020-12-15 21:15:12 +03:00
signal replyToMessage ( )
signal editMessage ( )
2020-11-15 01:50:12 +03:00
onClicked: {
if ( messageListItem . precalculatedValues . pageIsSelecting ) {
page . toggleMessageSelection ( myMessage ) ;
2020-12-04 06:12:00 +03:00
} else {
// Allow extra context to react to click
var extraContent = extraContentLoader . item
if ( extraContent && ( "clicked" in extraContent ) && ( typeof extraContent . clicked === "function" ) &&
mouseX >= extraContentLoader . x && mouseY >= extraContentLoader . y &&
mouseX < ( extraContentLoader . x + extraContentLoader . width ) &&
mouseY < ( extraContentLoader . y + extraContentLoader . height ) ) {
extraContent . clicked ( )
2020-12-24 06:42:41 +03:00
} else if ( webPagePreviewLoader . item ) {
webPagePreviewLoader . item . clicked ( )
2020-12-04 06:12:00 +03:00
}
2020-11-15 01:50:12 +03:00
}
}
2020-11-07 22:58:23 +03:00
onPressAndHold: {
2020-11-15 01:50:12 +03:00
if ( messageListItem . precalculatedValues . pageIsSelecting ) {
page . selectedMessages = [ ] ;
page . state = ""
} else {
contextMenuLoader . active = true ;
}
2020-11-07 22:58:23 +03:00
}
Loader {
id: contextMenuLoader
active: false
asynchronous: true
onStatusChanged: {
if ( status === Loader . Ready ) {
messageListItem . menu = item ;
messageListItem . openMenu ( ) ;
2020-11-05 16:05:33 +03:00
}
}
2020-11-07 22:58:23 +03:00
sourceComponent: Component {
ContextMenu {
Repeater {
2020-12-05 20:31:34 +03:00
model: ( extraContentLoader . item && ( "extraContextMenuItems" in extraContentLoader . item ) ) ?
extraContentLoader.item.extraContextMenuItems : 0
delegate: MenuItem {
visible: modelData . visible
text: modelData . name
onClicked: modelData . action ( )
}
2020-11-07 22:58:23 +03:00
}
MenuItem {
2020-12-15 21:15:12 +03:00
visible: messageListItem . canReplyToMessage
onClicked: messageListItem . replyToMessage ( )
2020-11-07 22:58:23 +03:00
text: qsTr ( "Reply to Message" )
}
MenuItem {
visible: myMessage . can_be_edited
2020-12-15 21:15:12 +03:00
onClicked: messageListItem . editMessage ( )
text: qsTr ( "Edit Message" )
2020-11-07 22:58:23 +03:00
}
MenuItem {
onClicked: {
2020-12-29 18:32:39 +03:00
Clipboard . text = Functions . getMessageText ( myMessage , true , userInformation . id , true ) ;
2020-11-07 22:58:23 +03:00
}
text: qsTr ( "Copy Message to Clipboard" )
}
2020-11-15 01:50:12 +03:00
MenuItem {
onClicked: {
page . toggleMessageSelection ( myMessage ) ;
}
text: qsTr ( "Select Message" )
}
2020-11-17 22:25:57 +03:00
MenuItem {
onClicked: {
2020-12-26 00:38:13 +03:00
if ( myMessage . is_pinned ) {
Remorse . popupAction ( page , qsTr ( "Message unpinned" ) , function ( ) { tdLibWrapper . unpinMessage ( page . chatInformation . id , messageId ) ;
pinnedMessageItem . requestCloseMessage ( ) ; } ) ;
} else {
tdLibWrapper . pinMessage ( page . chatInformation . id , messageId ) ;
}
2020-11-17 22:25:57 +03:00
}
2020-12-26 00:38:13 +03:00
text: myMessage . is_pinned ? qsTr ( "Unpin Message" ) : qsTr ( "Pin Message" )
2020-11-18 16:22:08 +03:00
visible: canPinMessages ( )
2020-11-17 22:25:57 +03:00
}
2020-11-07 22:58:23 +03:00
MenuItem {
onClicked: {
var chatId = page . chatInformation . id ;
2020-12-06 07:38:03 +03:00
var messageId = messageListItem . messageId ;
2020-11-07 22:58:23 +03:00
Remorse . itemAction ( messageListItem , qsTr ( "Message deleted" ) , function ( ) { tdLibWrapper . deleteMessages ( chatId , [ messageId ] ) ; } )
}
text: qsTr ( "Delete Message" )
visible: myMessage . can_be_deleted_for_all_users || ( myMessage . can_be_deleted_only_for_self && myMessage . chat_id === page . myUserId )
}
2020-11-05 16:05:33 +03:00
}
}
}
Connections {
target: chatModel
2020-11-16 01:05:22 +03:00
onMessagesReceived: {
messageBackground . isUnread = index > chatModel . getLastReadMessageIndex ( ) ;
}
onMessagesIncrementalUpdate: {
messageBackground . isUnread = index > chatModel . getLastReadMessageIndex ( ) ;
2020-11-05 16:05:33 +03:00
}
onNewMessageReceived: {
2020-11-16 01:05:22 +03:00
messageBackground . isUnread = index > chatModel . getLastReadMessageIndex ( ) ;
}
onUnreadCountUpdated: {
messageBackground . isUnread = index > chatModel . getLastReadMessageIndex ( ) ;
2020-11-05 16:05:33 +03:00
}
onLastReadSentMessageUpdated: {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatModel] Messages in this chat were read, new last read: " , lastReadSentIndex , ", updating description for index " , index , ", status: " , ( index <= lastReadSentIndex ) ) ;
2020-11-07 22:58:23 +03:00
messageDateText . text = getMessageStatusText ( myMessage , index , lastReadSentIndex , messageDateText . useElapsed ) ;
2020-11-05 16:05:33 +03:00
}
onMessageUpdated: {
if ( index === modelIndex ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatModel] This message was updated, index " , index , ", updating content..." ) ;
2020-11-07 22:58:23 +03:00
messageDateText . text = getMessageStatusText ( myMessage , index , chatView . lastReadSentIndex , messageDateText . useElapsed ) ;
2020-12-29 18:32:39 +03:00
messageText . text = Emoji . emojify ( Functions . getMessageText ( myMessage , false , page . myUserId , false ) , messageText . font . pixelSize ) ;
2020-11-05 16:05:33 +03:00
}
}
}
Connections {
target: tdLibWrapper
onReceivedMessage: {
2020-11-07 22:58:23 +03:00
if ( messageId === myMessage . reply_to_message_id . toString ( ) ) {
messageInReplyToLoader . inReplyToMessage = message ;
2020-11-05 16:05:33 +03:00
}
}
2020-12-25 17:33:53 +03:00
onMessageNotFound: {
if ( messageId === myMessage . reply_to_message_id ) {
messageInReplyToLoader . active = false ;
}
}
2020-11-05 16:05:33 +03:00
}
Component.onCompleted: {
delegateComponentLoadingTimer . start ( ) ;
2020-11-07 22:58:23 +03:00
if ( myMessage . reply_to_message_id !== 0 ) {
tdLibWrapper . getMessage ( page . chatInformation . id , myMessage . reply_to_message_id ) ;
}
2020-11-05 16:05:33 +03:00
}
Timer {
id: delegateComponentLoadingTimer
interval: 500
repeat: false
running: false
onTriggered: {
2020-11-07 22:58:23 +03:00
if ( typeof myMessage . content !== "undefined" ) {
2020-11-05 16:05:33 +03:00
if ( messageListItem . extraContentComponentName !== "" ) {
extraContentLoader . setSource (
"../components/" + messageListItem . extraContentComponentName + ".qml" ,
{
messageListItem: messageListItem
} )
} else {
2020-11-07 22:58:23 +03:00
if ( typeof myMessage . content . web_page !== "undefined" ) { // only in messageText
2020-11-05 16:05:33 +03:00
webPagePreviewLoader . active = true ;
}
}
}
}
}
Row {
id: messageTextRow
spacing: Theme . paddingSmall
2020-11-07 22:58:23 +03:00
width: precalculatedValues . entryWidth
anchors.centerIn: parent
2020-11-05 16:05:33 +03:00
Loader {
id: profileThumbnailLoader
2020-11-07 22:58:23 +03:00
active: precalculatedValues . showUserInfo
2020-11-05 16:05:33 +03:00
asynchronous: true
2020-11-07 22:58:23 +03:00
width: precalculatedValues . profileThumbnailDimensions
height: width
2020-11-05 16:05:33 +03:00
anchors.bottom: parent . bottom
anchors.bottomMargin: Theme . paddingSmall
sourceComponent: Component {
ProfileThumbnail {
id: messagePictureThumbnail
2020-12-25 15:50:13 +03:00
photoData: messageListItem . isAnonymous ? ( ( typeof page . chatInformation . photo !== "undefined" ) ? page.chatInformation.photo.small : { } ) : ( ( typeof messageListItem . userInformation . profile_photo !== "undefined" ) ? messageListItem.userInformation.profile_photo.small : ( { } ) )
2020-11-05 16:05:33 +03:00
replacementStringHint: userText . text
2020-11-07 22:58:23 +03:00
width: Theme . itemSizeSmall
height: Theme . itemSizeSmall
visible: precalculatedValues . showUserInfo
2020-11-05 16:05:33 +03:00
MouseArea {
anchors.fill: parent
2020-12-25 15:50:13 +03:00
enabled: ! ( messageListItem . precalculatedValues . pageIsSelecting || messageListItem . isAnonymous )
2020-11-05 16:05:33 +03:00
onClicked: {
tdLibWrapper . createPrivateChat ( messageListItem . userInformation . id ) ;
}
}
}
}
}
Item {
id: messageTextItem
2020-11-07 22:58:23 +03:00
width: precalculatedValues . textItemWidth
2020-11-05 16:05:33 +03:00
height: messageBackground . height
Rectangle {
id: messageBackground
2020-11-16 01:05:22 +03:00
2020-11-05 16:05:33 +03:00
anchors {
left: parent . left
2020-11-07 22:58:23 +03:00
leftMargin: messageListItem . isOwnMessage ? precalculatedValues.pageMarginDouble : 0
2020-11-05 16:05:33 +03:00
verticalCenter: parent . verticalCenter
}
2020-11-16 17:12:18 +03:00
height: messageTextColumn . height + precalculatedValues . paddingMediumDouble
2020-11-07 22:58:23 +03:00
width: precalculatedValues . backgroundWidth
2020-11-16 01:05:22 +03:00
property bool isUnread: index > chatModel . getLastReadMessageIndex ( )
2020-11-07 22:58:23 +03:00
color: isUnread ? Theme.secondaryHighlightColor : Theme . secondaryColor
2020-11-05 16:05:33 +03:00
radius: parent . width / 50
2020-11-07 22:58:23 +03:00
opacity: isUnread ? 0.5 : 0.2
visible: appSettings . showStickersAsImages || myMessage . content [ '@type' ] !== "messageSticker"
2020-11-05 16:05:33 +03:00
Behavior on color { ColorAnimation { duration: 200 } }
Behavior on opacity { FadeAnimation { } }
}
Column {
id: messageTextColumn
spacing: Theme . paddingSmall
2020-11-07 22:58:23 +03:00
width: precalculatedValues . textColumnWidth
2020-11-05 16:05:33 +03:00
anchors.centerIn: messageBackground
2020-11-22 02:39:49 +03:00
Label {
2020-11-05 16:05:33 +03:00
id: userText
width: parent . width
2020-12-25 15:50:13 +03:00
text: messageListItem . isOwnMessage ? qsTr ( "You" ) : Emoji . emojify ( messageListItem . isAnonymous ? page.chatInformation.title : Functions . getUserName ( messageListItem . userInformation ) , font . pixelSize )
2020-11-05 16:05:33 +03:00
font.pixelSize: Theme . fontSizeExtraSmall
font.weight: Font . ExtraBold
2020-11-07 22:58:23 +03:00
color: messageListItem . textColor
2020-11-05 16:05:33 +03:00
maximumLineCount: 1
2020-11-22 02:39:49 +03:00
truncationMode: TruncationMode . Fade
2020-11-05 16:05:33 +03:00
textFormat: Text . StyledText
2020-11-07 22:58:23 +03:00
horizontalAlignment: messageListItem . textAlign
visible: precalculatedValues . showUserInfo
2020-11-05 16:05:33 +03:00
MouseArea {
anchors.fill: parent
2020-12-25 15:50:13 +03:00
enabled: ! ( messageListItem . precalculatedValues . pageIsSelecting || messageListItem . isAnonymous )
2020-11-05 16:05:33 +03:00
onClicked: {
tdLibWrapper . createPrivateChat ( messageListItem . userInformation . id ) ;
}
}
}
2020-11-07 22:58:23 +03:00
Loader {
id: messageInReplyToLoader
active: myMessage . reply_to_message_id !== 0
width: parent . width
// text height ~= 1,28*font.pixelSize
height: active ? precalculatedValues.messageInReplyToHeight : 0
property var inReplyToMessage ;
sourceComponent: Component {
2020-11-17 22:25:57 +03:00
Item {
width: messageInReplyToRow . width
height: messageInReplyToRow . height
InReplyToRow {
id: messageInReplyToRow
myUserId: page . myUserId
visible: true
inReplyToMessage: messageInReplyToLoader . inReplyToMessage
}
MouseArea {
anchors.fill: parent
onClicked: {
messageOverlayLoader . overlayMessage = messageInReplyToRow . inReplyToMessage ;
messageOverlayLoader . active = true ;
}
}
2020-11-07 22:58:23 +03:00
}
}
2020-11-05 16:05:33 +03:00
}
Loader {
id: forwardedInformationLoader
2020-11-07 22:58:23 +03:00
active: typeof myMessage . forward_info !== "undefined"
2020-11-05 16:05:33 +03:00
asynchronous: true
width: parent . width
2020-11-07 22:58:23 +03:00
height: active ? ( item ? item.height : Theme . itemSizeExtraSmall ) : 0
2020-11-05 16:05:33 +03:00
sourceComponent: Component {
Row {
id: forwardedMessageInformationRow
spacing: Theme . paddingSmall
width: parent . width
Component.onCompleted: {
2020-11-07 22:58:23 +03:00
if ( myMessage . forward_info . origin [ "@type" ] === "messageForwardOriginChannel" ) {
var otherChatInformation = tdLibWrapper . getChat ( myMessage . forward_info . origin . chat_id ) ;
2020-11-15 01:50:12 +03:00
forwardedThumbnail . photoData = ( typeof otherChatInformation . photo !== "undefined" ) ? otherChatInformation.photo.small : { } ;
2020-11-05 16:05:33 +03:00
forwardedChannelText . text = Emoji . emojify ( otherChatInformation . title , Theme . fontSizeExtraSmall ) ;
2020-11-07 22:58:23 +03:00
} else if ( myMessage . forward_info . origin [ "@type" ] === "messageForwardOriginUser" ) {
2020-12-08 00:13:51 +03:00
var otherUserInformation = tdLibWrapper . getUserInformation ( myMessage . forward_info . origin . sender_user_id ) ;
2020-11-15 01:50:12 +03:00
forwardedThumbnail . photoData = ( typeof otherUserInformation . profile_photo !== "undefined" ) ? otherUserInformation.profile_photo.small : { } ;
2020-11-05 16:05:33 +03:00
forwardedChannelText . text = Emoji . emojify ( Functions . getUserName ( otherUserInformation ) , Theme . fontSizeExtraSmall ) ;
} else {
2020-11-14 22:02:34 +03:00
forwardedChannelText . text = Emoji . emojify ( myMessage . forward_info . origin . sender_name , Theme . fontSizeExtraSmall ) ;
forwardedThumbnail . photoData = { } ;
2020-11-05 16:05:33 +03:00
}
}
ProfileThumbnail {
id: forwardedThumbnail
replacementStringHint: forwardedChannelText . text
width: Theme . itemSizeExtraSmall
height: Theme . itemSizeExtraSmall
}
Column {
spacing: Theme . paddingSmall
2020-11-14 22:02:34 +03:00
width: parent . width - forwardedThumbnail . width - Theme . paddingSmall
2020-11-22 02:39:49 +03:00
Label {
2020-11-05 16:05:33 +03:00
font.pixelSize: Theme . fontSizeExtraSmall
width: parent . width
font.italic: true
2020-11-22 02:39:49 +03:00
truncationMode: TruncationMode . Fade
2020-11-05 16:05:33 +03:00
textFormat: Text . StyledText
text: qsTr ( "Forwarded Message" )
}
2020-11-22 02:39:49 +03:00
Label {
2020-11-05 16:05:33 +03:00
id: forwardedChannelText
font.pixelSize: Theme . fontSizeExtraSmall
color: Theme . primaryColor
width: parent . width
font.bold: true
2020-11-22 02:39:49 +03:00
truncationMode: TruncationMode . Fade
2020-11-05 16:05:33 +03:00
textFormat: Text . StyledText
text: Emoji . emojify ( forwardedMessageInformationRow . otherChatInformation . title , font . pixelSize )
}
}
}
}
}
Text {
id: messageText
width: parent . width
2020-12-29 18:32:39 +03:00
text: Emoji . emojify ( Functions . getMessageText ( myMessage , false , page . myUserId , false ) , font . pixelSize )
2020-11-05 16:05:33 +03:00
font.pixelSize: Theme . fontSizeSmall
2020-11-07 22:58:23 +03:00
color: messageListItem . textColor
2020-11-05 16:05:33 +03:00
wrapMode: Text . Wrap
textFormat: Text . StyledText
onLinkActivated: {
2020-12-27 02:01:59 +03:00
var chatCommand = Functions . handleLink ( link ) ;
if ( chatCommand ) {
tdLibWrapper . sendTextMessage ( chatInformation . id , chatCommand ) ;
}
2020-11-05 16:05:33 +03:00
}
2020-11-07 22:58:23 +03:00
horizontalAlignment: messageListItem . textAlign
2020-11-05 16:05:33 +03:00
linkColor: Theme . highlightColor
visible: ( text !== "" )
}
Loader {
id: webPagePreviewLoader
active: false
asynchronous: true
width: parent . width
2020-12-24 06:42:41 +03:00
height: ( status === Loader . Ready ) ? item.implicitHeight : myMessage . content . web_page ? precalculatedValues.webPagePreviewHeight : 0
2020-11-05 16:05:33 +03:00
sourceComponent: Component {
WebPagePreview {
2020-11-07 22:58:23 +03:00
webPageData: myMessage . content . web_page
2020-11-05 16:05:33 +03:00
width: parent . width
2020-12-05 00:47:03 +03:00
highlighted: messageListItem . highlighted
2020-11-05 16:05:33 +03:00
}
}
}
2020-12-04 22:29:31 +03:00
2020-11-05 16:05:33 +03:00
Loader {
id: extraContentLoader
width: parent . width
asynchronous: true
2020-11-07 22:58:23 +03:00
height: item ? item.height : ( messageListItem . extraContentComponentName !== "" ? chatView . getContentComponentHeight ( messageListItem . extraContentComponentName , myMessage . content , width ) : 0 )
2020-11-05 16:05:33 +03:00
}
2020-12-04 22:29:31 +03:00
Binding {
target: extraContentLoader . item
when: extraContentLoader . item && ( "highlighted" in extraContentLoader . item ) && ( typeof extraContentLoader . item . highlighted === "boolean" )
property: "highlighted"
value: messageListItem . highlighted
}
2020-12-27 02:01:59 +03:00
Loader {
id: replyMarkupLoader
width: parent . width
height: active ? ( myMessage . reply_markup . rows . length * ( Theme . itemSizeSmall + Theme . paddingSmall ) - Theme . paddingSmall ) : 0
asynchronous: true
active: ! ! myMessage . reply_markup && myMessage . reply_markup . rows
source: Qt . resolvedUrl ( "ReplyMarkupButtons.qml" )
}
2020-11-05 16:05:33 +03:00
Timer {
id: messageDateUpdater
interval: 60000
running: true
repeat: true
onTriggered: {
2020-11-07 22:58:23 +03:00
messageDateText . text = getMessageStatusText ( myMessage , index , chatView . lastReadSentIndex , messageDateText . useElapsed ) ;
2020-11-05 16:05:33 +03:00
}
}
Text {
width: parent . width
property bool useElapsed: true
id: messageDateText
font.pixelSize: Theme . fontSizeTiny
color: messageListItem . isOwnMessage ? Theme.secondaryHighlightColor : Theme . secondaryColor
2020-11-07 22:58:23 +03:00
horizontalAlignment: messageListItem . textAlign
text: getMessageStatusText ( myMessage , index , chatView . lastReadSentIndex , messageDateText . useElapsed )
2020-11-05 16:05:33 +03:00
MouseArea {
anchors.fill: parent
2020-11-15 01:50:12 +03:00
enabled: ! messageListItem . precalculatedValues . pageIsSelecting
2020-11-05 16:05:33 +03:00
onClicked: {
messageDateText . useElapsed = ! messageDateText . useElapsed ;
2020-11-07 22:58:23 +03:00
messageDateText . text = getMessageStatusText ( myMessage , index , chatView . lastReadSentIndex , messageDateText . useElapsed ) ;
2020-11-05 16:05:33 +03:00
}
}
}
}
}
}
}