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-07 22:58:23 +03:00
import QtQml . Models 2.3
2020-11-05 16:05:33 +03:00
ListItem {
id: messageListItem
contentHeight: messageBackground . height + Theme . paddingMedium
2020-11-08 22:37:17 +03:00
readonly property var myMessage: display
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 ) {
return existingMessage . id === myMessage . id ;
} ) ;
2020-11-08 22:37:17 +03:00
readonly property bool isOwnMessage: page . myUserId === myMessage . sender_user_id
readonly property string extraContentComponentName: typeof myMessage . content !== "undefined"
2020-11-07 22:58:23 +03:00
&& typeof chatView . contentComponentNames [ myMessage . content [ '@type' ] ] !== "undefined" ?
chatView . contentComponentNames [ myMessage . content [ '@type' ] ] : ""
2020-11-08 22:37:17 +03:00
readonly property ObjectModel additionalContextItems: ObjectModel { }
2020-11-15 01:50:12 +03:00
highlighted: down || isSelected
openMenuOnPressAndHold: ! messageListItem . precalculatedValues . pageIsSelecting
onClicked: {
if ( messageListItem . precalculatedValues . pageIsSelecting ) {
page . toggleMessageSelection ( myMessage ) ;
}
}
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 {
model: messageListItem . additionalContextItems
}
MenuItem {
onClicked: {
newMessageInReplyToRow . inReplyToMessage = myMessage ;
newMessageTextField . focus = true ;
}
text: qsTr ( "Reply to Message" )
}
MenuItem {
onClicked: {
newMessageColumn . editMessageId = myMessage . id ;
newMessageTextField . text = Functions . getMessageText ( myMessage , false , false ) ;
newMessageTextField . focus = true ;
}
text: qsTr ( "Edit Message" )
visible: myMessage . can_be_edited
}
MenuItem {
onClicked: {
Clipboard . text = Functions . getMessageText ( myMessage , true , false ) ;
}
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-07 22:58:23 +03:00
MenuItem {
onClicked: {
var chatId = page . chatInformation . id ;
var messageId = myMessage . id ;
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: {
console . 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 ) {
console . 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 ) ;
messageText . text = Emoji . emojify ( Functions . getMessageText ( myMessage , false , messageListItem . isOwnMessage ) , 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
}
}
}
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-11-15 01:50:12 +03:00
photoData: ( 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-11-15 01:50:12 +03:00
enabled: ! messageListItem . precalculatedValues . pageIsSelecting
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-07 22:58:23 +03:00
height: messageTextColumn . height + precalculatedValues . paddingMediumDouble
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
Text {
id: userText
width: parent . width
2020-11-07 22:58:23 +03:00
text: messageListItem . isOwnMessage ? qsTr ( "You" ) : Emoji . emojify ( 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
elide: Text . ElideRight
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-11-15 01:50:12 +03:00
enabled: ! messageListItem . precalculatedValues . pageIsSelecting
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 {
InReplyToRow {
id: messageInReplyToRow
myUserId: page . myUserId
visible: true
inReplyToMessage: messageInReplyToLoader . inReplyToMessage
}
}
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" ) {
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-05 16:05:33 +03:00
Text {
font.pixelSize: Theme . fontSizeExtraSmall
color: Theme . primaryColor
width: parent . width
font.italic: true
elide: Text . ElideRight
textFormat: Text . StyledText
text: qsTr ( "Forwarded Message" )
onTruncatedChanged: {
// There is obviously a bug in QML in truncating text with images.
// We simply remove Emojis then...
if ( truncated ) {
text = text . replace ( /\<img [^>]+\/\>/g , "" ) ;
}
}
}
Text {
id: forwardedChannelText
font.pixelSize: Theme . fontSizeExtraSmall
color: Theme . primaryColor
width: parent . width
font.bold: true
elide: Text . ElideRight
textFormat: Text . StyledText
text: Emoji . emojify ( forwardedMessageInformationRow . otherChatInformation . title , font . pixelSize )
onTruncatedChanged: {
// There is obviously a bug in QML in truncating text with images.
// We simply remove Emojis then...
if ( truncated ) {
text = text . replace ( /\<img [^>]+\/\>/g , "" ) ;
}
}
}
}
}
}
}
Text {
id: messageText
width: parent . width
2020-11-07 22:58:23 +03:00
text: Emoji . emojify ( Functions . getMessageText ( myMessage , false , messageListItem . isOwnMessage ) , 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: {
Functions . handleLink ( link ) ;
}
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-11-07 22:58:23 +03:00
height: typeof myMessage . content . web_page !== "undefined" ? precalculatedValues.webPagePreviewHeight : 0
2020-11-05 16:05:33 +03:00
sourceComponent: Component {
id: webPagePreviewComponent
WebPagePreview {
id: webPagePreview
2020-11-15 00:53:51 +03:00
Component.onCompleted: {
webPagePreviewLoader . height = 0 ;
}
2020-11-05 16:05:33 +03:00
onImplicitHeightChanged: {
webPagePreviewLoader . height = webPagePreview . implicitHeight ;
}
2020-11-07 22:58:23 +03:00
webPageData: myMessage . content . web_page
2020-11-05 16:05:33 +03:00
width: parent . width
}
}
}
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
}
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
}
}
}
}
}
}
}