2020-08-21 15:26:56 +03:00
/ *
2020-10-19 20:34:47 +03:00
Copyright ( C ) 2020 Sebastian J . Wolf and other contributors
2020-08-21 15:26:56 +03:00
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/>.
* /
2020-10-31 22:49:03 +03:00
import QtQuick 2.6
2020-11-18 16:22:08 +03:00
import QtGraphicalEffects 1.0
2020-08-21 15:26:56 +03:00
import Sailfish . Silica 1.0
2020-09-27 14:49:06 +03:00
import Sailfish . Pickers 1.0
2020-09-28 00:24:22 +03:00
import Nemo . Thumbnailer 1.0
2020-08-21 15:26:56 +03:00
import WerkWolf . Fernschreiber 1.0
import "../components"
2020-11-20 23:58:26 +03:00
import "../js/debug.js" as Debug
2020-08-21 15:26:56 +03:00
import "../js/twemoji.js" as Emoji
import "../js/functions.js" as Functions
Page {
id: chatPage
allowedOrientations: Orientation . All
2020-10-16 00:43:55 +03:00
backNavigation: ! stickerPickerLoader . active
2020-08-21 15:26:56 +03:00
property bool loading: true ;
2020-08-30 00:59:29 +03:00
property bool isInitialized: false ;
2020-11-08 22:37:17 +03:00
readonly property int myUserId: tdLibWrapper . getUserInformation ( ) . id ;
2020-11-06 01:23:37 +03:00
property var chatInformation ;
2020-11-25 02:23:38 +03:00
property var secretChatDetails ;
2020-11-20 22:39:23 +03:00
property alias chatPicture: chatPictureThumbnail . photoData
2020-08-21 19:03:51 +03:00
property bool isPrivateChat: false ;
2020-11-25 02:23:38 +03:00
property bool isSecretChat: false ;
2020-11-26 02:25:15 +03:00
property bool isSecretChatReady: false ;
2020-08-21 19:03:51 +03:00
property bool isBasicGroup: false ;
property bool isSuperGroup: false ;
property bool isChannel: false ;
2020-11-06 01:23:37 +03:00
property var chatPartnerInformation ;
property var chatGroupInformation ;
2020-08-21 19:03:51 +03:00
property int chatOnlineMemberCount: 0 ;
2020-11-06 01:23:37 +03:00
property var emojiProposals ;
2020-11-16 18:52:48 +03:00
property bool iterativeInitialization: false ;
2020-11-26 02:25:15 +03:00
readonly property bool userIsMember: ( ( isPrivateChat || isSecretChat ) && chatInformation [ "@type" ] ) || // should be optimized
2020-11-07 22:29:44 +03:00
( isBasicGroup || isSuperGroup ) && (
( chatGroupInformation . status [ "@type" ] === "chatMemberStatusMember" )
|| ( chatGroupInformation . status [ "@type" ] === "chatMemberStatusAdministrator" )
|| ( chatGroupInformation . status [ "@type" ] === "chatMemberStatusRestricted" && chatGroupInformation . status . is_member )
|| ( chatGroupInformation . status [ "@type" ] === "chatMemberStatusCreator" && chatGroupInformation . status . is_member )
)
2020-11-15 01:50:12 +03:00
property var selectedMessages: [ ]
2020-12-06 17:50:03 +03:00
readonly property bool isSelecting: selectedMessages . length > 0
2020-12-15 21:15:12 +03:00
readonly property bool canSendMessages: hasSendPrivilege ( "can_send_messages" )
2020-11-15 01:50:12 +03:00
states: [
State {
name: "selectMessages"
2020-12-06 17:50:03 +03:00
when: isSelecting
2020-11-15 01:50:12 +03:00
PropertyChanges {
target: chatNameText
text: qsTr ( "Select Messages" )
}
PropertyChanges {
target: chatStatusText
2020-11-19 11:32:05 +03:00
text: qsTr ( "%Ln messages selected" , "number of messages selected" , chatPage . selectedMessages . length )
2020-11-15 01:50:12 +03:00
}
PropertyChanges {
target: newMessageTextField
focus: false
}
}
]
2020-12-15 21:15:12 +03:00
2020-11-15 01:50:12 +03:00
function toggleMessageSelection ( message ) {
var selectionArray = selectedMessages ;
var foundIndex = - 1
if ( selectionArray . length > 0 ) {
for ( var i = 0 ; i < selectionArray . length ; i += 1 ) {
if ( selectionArray [ i ] . id === message . id ) {
foundIndex = i ;
continue ;
}
}
}
if ( foundIndex > - 1 ) {
selectionArray . splice ( foundIndex , 1 ) ;
} else {
selectionArray . push ( message ) ;
}
selectedMessages = selectionArray ;
}
2020-08-21 19:03:51 +03:00
function updateChatPartnerStatusText ( ) {
2020-12-06 17:50:03 +03:00
if ( chatPage . isSelecting ) {
2020-11-18 17:50:23 +03:00
return
}
2020-10-19 13:20:02 +03:00
var statusText = Functions . getChatPartnerStatusText ( chatPartnerInformation . status [ '@type' ] , chatPartnerInformation . status . was_online ) ;
2020-11-25 02:23:38 +03:00
if ( chatPage . secretChatDetails ) {
2020-11-26 02:25:15 +03:00
var secretChatStatus = Functions . getSecretChatStatus ( chatPage . secretChatDetails ) ;
if ( statusText && secretChatStatus ) {
2020-11-25 02:23:38 +03:00
statusText += " - " ;
}
2020-11-26 02:25:15 +03:00
if ( secretChatStatus ) {
statusText += secretChatStatus ;
}
2020-11-25 02:23:38 +03:00
}
if ( statusText ) {
2020-10-19 13:20:02 +03:00
chatStatusText . text = statusText ;
2020-08-21 19:03:51 +03:00
}
}
function updateGroupStatusText ( ) {
2020-12-06 17:50:03 +03:00
if ( chatPage . isSelecting ) {
2020-11-17 18:21:20 +03:00
return
}
2020-08-21 19:03:51 +03:00
if ( chatOnlineMemberCount > 0 ) {
2020-11-18 17:50:23 +03:00
chatStatusText . text = qsTr ( "%1, %2" , "combination of '[x members], [y online]', which are separate translations" )
. arg ( qsTr ( "%1 members" , "" , chatGroupInformation . member_count )
. arg ( Functions . getShortenedCount ( chatGroupInformation . member_count ) ) )
. arg ( qsTr ( "%1 online" , "" , chatOnlineMemberCount )
. arg ( Functions . getShortenedCount ( chatOnlineMemberCount ) ) ) ;
2020-08-21 19:03:51 +03:00
} else {
if ( isChannel ) {
2020-11-18 17:50:23 +03:00
chatStatusText . text = qsTr ( "%1 subscribers" , "" , chatGroupInformation . member_count ) . arg ( Functions . getShortenedCount ( chatGroupInformation . member_count ) ) ;
2020-08-21 19:03:51 +03:00
} else {
2020-11-18 17:50:23 +03:00
chatStatusText . text = qsTr ( "%1 members" , "" , chatGroupInformation . member_count ) . arg ( Functions . getShortenedCount ( chatGroupInformation . member_count ) ) ;
2020-08-21 19:03:51 +03:00
}
}
2020-11-07 22:29:44 +03:00
joinLeaveChatMenuItem . text = chatPage . userIsMember ? qsTr ( "Leave Chat" ) : qsTr ( "Join Chat" ) ;
2020-08-21 19:03:51 +03:00
}
2020-08-21 15:26:56 +03:00
function initializePage ( ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Initializing chat page..." ) ;
2020-08-29 22:39:57 +03:00
chatView . currentIndex = - 1 ;
2020-08-30 20:04:16 +03:00
chatView . lastReadSentIndex = 0 ;
2020-08-21 19:03:51 +03:00
var chatType = chatInformation . type [ '@type' ] ;
2020-11-26 02:25:15 +03:00
isPrivateChat = chatType === "chatTypePrivate" ;
2020-11-25 02:23:38 +03:00
isSecretChat = chatType === "chatTypeSecret" ;
2020-08-21 19:03:51 +03:00
isBasicGroup = ( chatType === "chatTypeBasicGroup" ) ;
isSuperGroup = ( chatType === "chatTypeSupergroup" ) ;
2020-11-26 02:25:15 +03:00
if ( isPrivateChat || isSecretChat ) {
2020-08-21 19:03:51 +03:00
chatPartnerInformation = tdLibWrapper . getUserInformation ( chatInformation . type . user_id ) ;
updateChatPartnerStatusText ( ) ;
2020-11-25 02:23:38 +03:00
if ( isSecretChat ) {
tdLibWrapper . getSecretChat ( chatInformation . type . secret_chat_id ) ;
}
2020-08-21 19:03:51 +03:00
}
2020-11-08 22:37:17 +03:00
else if ( isBasicGroup ) {
2020-08-21 19:03:51 +03:00
chatGroupInformation = tdLibWrapper . getBasicGroup ( chatInformation . type . basic_group_id ) ;
updateGroupStatusText ( ) ;
}
2020-11-08 22:37:17 +03:00
else if ( isSuperGroup ) {
2020-08-21 19:03:51 +03:00
chatGroupInformation = tdLibWrapper . getSuperGroup ( chatInformation . type . supergroup_id ) ;
isChannel = chatGroupInformation . is_channel ;
updateGroupStatusText ( ) ;
}
2020-10-17 00:00:08 +03:00
if ( stickerManager . needsReload ( ) ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Stickers will be reloaded!" ) ;
2020-10-17 00:00:08 +03:00
tdLibWrapper . getRecentStickers ( ) ;
tdLibWrapper . getInstalledStickerSets ( ) ;
stickerManager . setNeedsReload ( false ) ;
}
2020-12-26 00:38:13 +03:00
tdLibWrapper . getChatPinnedMessage ( chatInformation . id ) ;
2020-12-31 02:19:36 +03:00
tdLibWrapper . toggleChatIsMarkedAsUnread ( chatInformation . id , false ) ;
2020-08-21 15:26:56 +03:00
}
2020-11-03 01:42:23 +03:00
function getMessageStatusText ( message , listItemIndex , lastReadSentIndex , useElapsed ) {
2020-08-31 22:51:52 +03:00
var messageStatusSuffix = "" ;
2020-10-19 13:20:02 +03:00
if ( ! message ) {
return "" ;
}
2020-09-19 21:33:51 +03:00
if ( message . edit_date > 0 ) {
2020-09-20 01:13:42 +03:00
messageStatusSuffix += " - " + qsTr ( "edited" ) ;
2020-09-19 21:33:51 +03:00
}
2020-12-07 23:43:09 +03:00
if ( chatPage . myUserId === message . sender . user_id ) {
2020-08-31 22:51:52 +03:00
messageStatusSuffix += " "
if ( listItemIndex <= lastReadSentIndex ) {
// Read by other party
messageStatusSuffix += Emoji . emojify ( "✅" , Theme . fontSizeTiny ) ;
} else {
// Not yet read by other party
if ( message . sending_state ) {
if ( message . sending_state [ '@type' ] === "messageSendingStatePending" ) {
messageStatusSuffix += Emoji . emojify ( "🕙" , Theme . fontSizeTiny ) ;
} else {
// Sending failed...
messageStatusSuffix += Emoji . emojify ( "❌" , Theme . fontSizeTiny ) ;
}
} else {
messageStatusSuffix += Emoji . emojify ( "☑️" , Theme . fontSizeTiny ) ;
}
}
}
2020-11-03 01:42:23 +03:00
return ( useElapsed ? Functions . getDateTimeElapsed ( message . date ) : Functions . getDateTimeTranslated ( message . date ) ) + messageStatusSuffix ;
2020-08-31 22:51:52 +03:00
}
2020-09-27 14:49:06 +03:00
function clearAttachmentPreviewRow ( ) {
attachmentPreviewRow . visible = false ;
attachmentPreviewRow . isPicture = false ;
2020-09-28 00:24:22 +03:00
attachmentPreviewRow . isVideo = false ;
attachmentPreviewRow . isDocument = false ;
2021-01-02 19:22:09 +03:00
attachmentPreviewRow . isVoiceNote = false ;
2021-01-03 03:22:30 +03:00
attachmentPreviewRow . isLocation = false ;
2020-09-29 00:08:22 +03:00
attachmentPreviewRow . fileProperties = { } ;
2021-01-03 03:22:30 +03:00
attachmentPreviewRow . locationData = { } ;
2021-01-02 19:22:09 +03:00
attachmentPreviewRow . attachmentDescription = "" ;
2021-01-03 03:22:30 +03:00
fernschreiberUtils . stopGeoLocationUpdates ( ) ;
2020-09-29 00:08:22 +03:00
}
2020-09-29 22:00:23 +03:00
function controlSendButton ( ) {
2020-09-29 00:08:22 +03:00
if ( newMessageTextField . text . length !== 0
|| attachmentPreviewRow . isPicture
|| attachmentPreviewRow . isDocument
2021-01-02 19:22:09 +03:00
|| attachmentPreviewRow . isVideo
2021-01-03 03:22:30 +03:00
|| attachmentPreviewRow . isVoiceNote
|| attachmentPreviewRow . isLocation ) {
2020-09-29 00:08:22 +03:00
newMessageSendButton . enabled = true ;
} else {
newMessageSendButton . enabled = false ;
}
2020-09-27 14:49:06 +03:00
}
function sendMessage ( ) {
if ( newMessageColumn . editMessageId !== "0" ) {
tdLibWrapper . editMessageText ( chatInformation . id , newMessageColumn . editMessageId , newMessageTextField . text ) ;
} else {
if ( attachmentPreviewRow . visible ) {
if ( attachmentPreviewRow . isPicture ) {
2020-09-29 00:08:22 +03:00
tdLibWrapper . sendPhotoMessage ( chatInformation . id , attachmentPreviewRow . fileProperties . filePath , newMessageTextField . text , newMessageColumn . replyToMessageId ) ;
2020-09-27 14:49:06 +03:00
}
2020-09-28 00:24:22 +03:00
if ( attachmentPreviewRow . isVideo ) {
2020-09-29 00:08:22 +03:00
tdLibWrapper . sendVideoMessage ( chatInformation . id , attachmentPreviewRow . fileProperties . filePath , newMessageTextField . text , newMessageColumn . replyToMessageId ) ;
2020-09-28 00:24:22 +03:00
}
if ( attachmentPreviewRow . isDocument ) {
2020-09-29 00:08:22 +03:00
tdLibWrapper . sendDocumentMessage ( chatInformation . id , attachmentPreviewRow . fileProperties . filePath , newMessageTextField . text , newMessageColumn . replyToMessageId ) ;
2020-09-28 00:24:22 +03:00
}
2021-01-02 19:22:09 +03:00
if ( attachmentPreviewRow . isVoiceNote ) {
tdLibWrapper . sendVoiceNoteMessage ( chatInformation . id , fernschreiberUtils . voiceNotePath ( ) , newMessageTextField . text , newMessageColumn . replyToMessageId ) ;
}
2021-01-03 03:22:30 +03:00
if ( attachmentPreviewRow . isLocation ) {
tdLibWrapper . sendLocationMessage ( chatInformation . id , attachmentPreviewRow . locationData . latitude , attachmentPreviewRow . locationData . longitude , attachmentPreviewRow . locationData . horizontalAccuracy , newMessageColumn . replyToMessageId ) ;
}
2020-09-27 14:49:06 +03:00
clearAttachmentPreviewRow ( ) ;
} else {
tdLibWrapper . sendTextMessage ( chatInformation . id , newMessageTextField . text , newMessageColumn . replyToMessageId ) ;
}
2021-01-01 03:30:23 +03:00
if ( appSettings . focusTextAreaAfterSend ) {
lostFocusTimer . start ( ) ;
}
2020-09-27 14:49:06 +03:00
}
2020-09-29 22:00:23 +03:00
controlSendButton ( ) ;
2020-10-18 22:54:15 +03:00
newMessageInReplyToRow . inReplyToMessage = null ;
newMessageColumn . editMessageId = "0" ;
2021-01-03 03:22:30 +03:00
fernschreiberUtils . stopGeoLocationUpdates ( ) ;
2020-09-27 14:49:06 +03:00
}
2020-10-18 19:57:01 +03:00
function getWordBoundaries ( text , cursorPosition ) {
var wordBoundaries = { beginIndex : 0 , endIndex : text . length } ;
var currentIndex = 0 ;
for ( currentIndex = ( cursorPosition - 1 ) ; currentIndex > 0 ; currentIndex -- ) {
if ( text . charAt ( currentIndex ) === ' ' ) {
wordBoundaries . beginIndex = currentIndex + 1 ;
break ;
}
}
for ( currentIndex = cursorPosition ; currentIndex < text . length ; currentIndex ++ ) {
if ( text . charAt ( currentIndex ) === ' ' ) {
wordBoundaries . endIndex = currentIndex ;
break ;
}
}
return wordBoundaries ;
}
function handleMessageTextReplacement ( text , cursorPosition ) {
var wordBoundaries = getWordBoundaries ( text , cursorPosition ) ;
var currentWord = text . substring ( wordBoundaries . beginIndex , wordBoundaries . endIndex ) ;
if ( currentWord . length > 1 && currentWord . charAt ( 0 ) === ':' ) {
tdLibWrapper . searchEmoji ( currentWord . substring ( 1 ) ) ;
} else {
chatPage . emojiProposals = null ;
}
2020-11-29 01:00:10 +03:00
if ( currentWord . length > 1 && currentWord . charAt ( 0 ) === '@' ) {
knownUsersRepeater . model = knownUsersProxyModel ;
knownUsersProxyModel . setFilterWildcard ( "*" + currentWord . substring ( 1 ) + "*" ) ;
} else {
knownUsersRepeater . model = undefined ;
}
2020-10-18 19:57:01 +03:00
}
function replaceMessageText ( text , cursorPosition , newText ) {
var wordBoundaries = getWordBoundaries ( text , cursorPosition ) ;
var newCompleteText = text . substring ( 0 , wordBoundaries . beginIndex ) + newText + " " + text . substring ( wordBoundaries . endIndex ) ;
var newIndex = wordBoundaries . beginIndex + newText . length + 1 ;
newMessageTextField . text = newCompleteText ;
newMessageTextField . cursorPosition = newIndex ;
lostFocusTimer . start ( ) ;
}
2020-11-15 01:50:12 +03:00
function forwardMessages ( fromChatId , messageIds ) {
forwardMessagesTimer . fromChatId = fromChatId ;
forwardMessagesTimer . messageIds = messageIds ;
forwardMessagesTimer . start ( ) ;
}
2020-11-16 12:28:16 +03:00
function hasSendPrivilege ( privilege ) {
2020-11-18 13:13:05 +03:00
var groupStatus = chatGroupInformation ? chatGroupInformation.status : null
var groupStatusType = groupStatus ? groupStatus [ "@type" ] : null
2020-11-17 13:14:36 +03:00
return chatPage . isPrivateChat
|| ( groupStatusType === "chatMemberStatusMember" && chatInformation . permissions [ privilege ] )
|| groupStatusType === "chatMemberStatusAdministrator"
|| groupStatusType === "chatMemberStatusCreator"
|| ( groupStatusType === "chatMemberStatusRestricted" && groupStatus . permissions [ privilege ] )
2020-11-26 02:25:15 +03:00
|| ( chatPage . isSecretChat && chatPage . isSecretChatReady )
2020-11-16 12:28:16 +03:00
}
2020-11-18 16:22:08 +03:00
function canPinMessages ( ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "Can we pin messages?" ) ;
2020-11-26 02:25:15 +03:00
if ( chatPage . isPrivateChat || chatPage . isSecretChat ) {
Debug . log ( "Private/Secret Chat: No!" ) ;
2020-11-18 16:22:08 +03:00
return false ;
}
if ( chatPage . chatGroupInformation . status [ "@type" ] === "chatMemberStatusCreator" ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "Creator of this chat: Yes!" ) ;
2020-11-18 16:22:08 +03:00
return true ;
}
if ( chatPage . chatInformation . permissions . can_pin_messages ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "All people can pin: Yes!" ) ;
2020-11-18 16:22:08 +03:00
return true ;
}
if ( chatPage . chatGroupInformation . status [ "@type" ] === "chatMemberStatusAdministrator" ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "Admin with privileges? " , chatPage . chatGroupInformation . status . can_pin_messages ) ;
2020-11-18 16:22:08 +03:00
return chatPage . chatGroupInformation . status . can_pin_messages ;
}
if ( chatPage . chatGroupInformation . status [ "@type" ] === "chatMemberStatusRestricted" ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "Restricted, but can pin messages? " , chatPage . chatGroupInformation . status . permissions . can_pin_messages ) ;
2020-11-18 16:22:08 +03:00
return chatPage . chatGroupInformation . status . permissions . can_pin_messages ;
}
2020-11-20 23:58:26 +03:00
Debug . log ( "Something else: No!" ) ;
2020-11-18 16:22:08 +03:00
return false ;
}
2020-11-16 12:28:16 +03:00
2020-12-27 02:16:25 +03:00
function resetFocus ( ) {
if ( searchInChatField . text === "" ) {
chatOverviewItem . visible = true ;
}
searchInChatField . focus = false ;
chatPage . focus = true ;
}
2020-11-15 01:50:12 +03:00
Timer {
id: forwardMessagesTimer
interval: 200
property string fromChatId
property var messageIds
onTriggered: {
if ( chatPage . loading ) {
forwardMessagesTimer . start ( )
} else {
var forwardedToSecretChat = chatInformation . type [ "@type" ] === "chatTypeSecret" ;
tdLibWrapper . forwardMessages ( chatInformation . id , fromChatId , messageIds , forwardedToSecretChat , false ) ;
}
}
}
2020-10-18 19:57:01 +03:00
2020-12-27 02:16:25 +03:00
Timer {
id: searchInChatTimer
interval: 300
running: false
repeat: false
onTriggered: {
Debug . log ( "Searching for '" + searchInChatField . text + "'" ) ;
chatModel . setSearchQuery ( searchInChatField . text ) ;
}
}
2020-08-21 15:26:56 +03:00
Component.onCompleted: {
initializePage ( ) ;
}
2020-10-19 13:20:02 +03:00
Component.onDestruction: {
2020-12-31 21:27:44 +03:00
if ( chatPage . canSendMessages ) {
tdLibWrapper . setChatDraftMessage ( chatInformation . id , 0 , newMessageColumn . replyToMessageId , newMessageTextField . text ) ;
}
2021-01-03 03:22:30 +03:00
fernschreiberUtils . stopGeoLocationUpdates ( ) ;
2020-10-19 13:20:02 +03:00
tdLibWrapper . closeChat ( chatInformation . id ) ;
}
2020-08-21 15:47:08 +03:00
onStatusChanged: {
2020-10-19 13:20:02 +03:00
switch ( status ) {
case PageStatus.Activating:
2020-08-25 00:02:08 +03:00
tdLibWrapper . openChat ( chatInformation . id ) ;
2020-10-19 13:20:02 +03:00
break ;
case PageStatus.Active:
2020-08-30 00:59:29 +03:00
if ( ! chatPage . isInitialized ) {
chatModel . initialize ( chatInformation ) ;
2020-11-16 23:38:55 +03:00
2020-11-17 12:09:06 +03:00
pageStack . pushAttached ( Qt . resolvedUrl ( "ChatInformationPage.qml" ) , { "chatInformation" : chatInformation , "privateChatUserInformation" : chatPartnerInformation , "groupInformation" : chatGroupInformation , "chatOnlineMemberCount" : chatOnlineMemberCount } ) ;
2020-08-30 00:59:29 +03:00
chatPage . isInitialized = true ;
2020-12-31 02:59:05 +03:00
if ( chatInformation . draft_message ) {
if ( chatInformation . draft_message && chatInformation . draft_message . input_message_text ) {
newMessageTextField . text = chatInformation . draft_message . input_message_text . text . text ;
if ( chatInformation . draft_message . reply_to_message_id ) {
tdLibWrapper . getMessage ( chatInformation . id , chatInformation . draft_message . reply_to_message_id ) ;
}
}
}
2020-08-30 00:59:29 +03:00
}
2020-10-19 13:20:02 +03:00
break ;
2020-11-20 22:39:23 +03:00
case PageStatus.Inactive:
2020-11-21 12:12:59 +03:00
if ( pageStack . depth === 1 ) {
// Only clear chat model if navigated back to overview page. In other cases we keep the information...
chatModel . clear ( ) ;
}
2020-11-20 22:39:23 +03:00
break ;
2020-08-29 19:04:23 +03:00
}
2020-08-21 15:47:08 +03:00
}
2020-08-21 19:03:51 +03:00
Connections {
target: tdLibWrapper
onUserUpdated: {
2020-11-26 02:25:15 +03:00
if ( ( isPrivateChat || isSecretChat ) && chatPartnerInformation . id . toString ( ) === userId ) {
2020-08-21 19:03:51 +03:00
chatPartnerInformation = userInformation ;
updateChatPartnerStatusText ( ) ;
}
}
onBasicGroupUpdated: {
if ( isBasicGroup && chatGroupInformation . id . toString ( ) === groupId ) {
chatGroupInformation = groupInformation ;
updateGroupStatusText ( ) ;
}
}
onSuperGroupUpdated: {
if ( isSuperGroup && chatGroupInformation . id . toString ( ) === groupId ) {
chatGroupInformation = groupInformation ;
updateGroupStatusText ( ) ;
}
}
onChatOnlineMemberCountUpdated: {
2020-11-20 23:58:26 +03:00
Debug . log ( isSuperGroup , "/" , isBasicGroup , "/" , chatInformation . id . toString ( ) , "/" , chatId ) ;
2020-08-21 19:03:51 +03:00
if ( ( isSuperGroup || isBasicGroup ) && chatInformation . id . toString ( ) === chatId ) {
chatOnlineMemberCount = onlineMemberCount ;
updateGroupStatusText ( ) ;
}
}
2020-09-30 00:37:56 +03:00
onFileUpdated: {
uploadStatusRow . visible = fileInformation . remote . is_uploading_active ;
2020-11-14 23:01:52 +03:00
if ( uploadStatusRow . visible ) {
uploadingProgressBar . maximumValue = fileInformation . size ;
uploadingProgressBar . value = fileInformation . remote . uploaded_size ;
}
2020-09-30 00:37:56 +03:00
}
2020-10-18 19:57:01 +03:00
onEmojiSearchSuccessful: {
chatPage . emojiProposals = result ;
}
2020-11-16 01:29:04 +03:00
onErrorReceived: {
2020-11-16 16:37:14 +03:00
Functions . handleErrorMessage ( code , message ) ;
2020-11-16 01:29:04 +03:00
}
2020-11-16 23:39:11 +03:00
onReceivedMessage: {
2020-12-26 00:38:13 +03:00
if ( message . is_pinned ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Received pinned message" ) ;
2020-11-16 23:39:11 +03:00
pinnedMessageItem . pinnedMessage = message ;
}
2021-01-01 23:06:38 +03:00
if ( chatInformation . draft_message && messageId === chatInformation . draft_message . reply_to_message_id ) {
2020-12-31 02:59:05 +03:00
newMessageInReplyToRow . inReplyToMessage = message ;
}
2020-11-16 23:39:11 +03:00
}
2020-11-25 02:23:38 +03:00
onSecretChatReceived: {
2020-11-28 21:11:51 +03:00
if ( secretChatId === chatInformation . type . secret_chat_id ) {
2020-11-25 02:23:38 +03:00
Debug . log ( "[ChatPage] Received detailed information about this secret chat" ) ;
chatPage . secretChatDetails = secretChat ;
updateChatPartnerStatusText ( ) ;
2020-11-26 02:25:15 +03:00
chatPage . isSecretChatReady = chatPage . secretChatDetails . state [ "@type" ] === "secretChatStateReady" ;
2020-11-25 02:23:38 +03:00
}
}
onSecretChatUpdated: {
2020-11-27 21:42:39 +03:00
if ( secretChatId . toString ( ) === chatInformation . type . secret_chat_id . toString ( ) ) {
2020-11-25 02:23:38 +03:00
Debug . log ( "[ChatPage] Detailed information about this secret chat was updated" ) ;
chatPage . secretChatDetails = secretChat ;
updateChatPartnerStatusText ( ) ;
2020-11-26 02:25:15 +03:00
chatPage . isSecretChatReady = chatPage . secretChatDetails . state [ "@type" ] === "secretChatStateReady" ;
2020-11-25 02:23:38 +03:00
}
}
2020-08-21 19:03:51 +03:00
}
2020-08-22 22:43:20 +03:00
Connections {
target: chatModel
onMessagesReceived: {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Messages received, view has " , chatView . count , " messages, setting view to index " , modelIndex , ", own messages were read before index " , lastReadSentIndex ) ;
2020-11-16 18:52:48 +03:00
if ( totalCount === 0 ) {
if ( chatPage . iterativeInitialization ) {
chatPage . iterativeInitialization = false ;
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] actually, skipping that: No Messages in Chat." ) ;
2020-11-16 18:52:48 +03:00
chatView . positionViewAtEnd ( ) ;
chatPage . loading = false ;
return ;
} else {
chatPage . iterativeInitialization = true ;
}
2020-10-19 13:20:02 +03:00
}
2020-08-30 20:04:16 +03:00
chatView . lastReadSentIndex = lastReadSentIndex ;
2020-11-02 22:54:18 +03:00
chatView . scrollToIndex ( modelIndex ) ;
2020-09-14 00:25:48 +03:00
chatPage . loading = false ;
2020-12-28 19:12:21 +03:00
if ( chatOverviewItem . visible && modelIndex >= ( chatView . count - 10 ) ) {
2020-11-16 01:05:22 +03:00
chatView . inCooldown = true ;
chatModel . triggerLoadMoreFuture ( ) ;
}
2020-09-16 22:12:39 +03:00
if ( chatView . height > chatView . contentHeight ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Chat content quite small..." ) ;
2020-11-04 14:26:57 +03:00
viewMessageTimer . queueViewMessage ( chatView . count - 1 ) ;
2020-09-16 22:12:39 +03:00
}
2020-12-28 19:12:21 +03:00
chatViewCooldownTimer . restart ( ) ;
2020-08-22 22:43:20 +03:00
}
2020-08-23 00:49:02 +03:00
onNewMessageReceived: {
2020-12-07 23:43:09 +03:00
if ( chatView . manuallyScrolledToBottom || message . sender . user_id === chatPage . myUserId ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Own message received or was scrolled to bottom, scrolling down to see it..." ) ;
2020-11-02 22:54:18 +03:00
chatView . scrollToIndex ( chatView . count - 1 ) ;
2020-09-22 21:32:35 +03:00
}
2020-08-23 00:49:02 +03:00
}
2020-08-29 22:39:57 +03:00
onUnreadCountUpdated: {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Unread count updated, new count: " , unreadCount ) ;
2020-08-29 22:39:57 +03:00
chatInformation . unread_count = unreadCount ;
2020-12-28 19:12:21 +03:00
chatUnreadMessagesItem . visible = ( ! chatPage . loading && chatInformation . unread_count > 0 && chatOverviewItem . visible ) ;
2020-09-15 23:47:39 +03:00
chatUnreadMessagesCount . text = unreadCount > 99 ? "99+" : unreadCount ;
2020-08-29 22:39:57 +03:00
}
2020-08-30 20:04:16 +03:00
onLastReadSentMessageUpdated: {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Updating last read sent index, new index: " , lastReadSentIndex ) ;
2020-08-30 20:04:16 +03:00
chatView . lastReadSentIndex = lastReadSentIndex ;
}
2020-09-01 23:37:48 +03:00
onMessagesIncrementalUpdate: {
2020-11-20 23:58:26 +03:00
Debug . log ( "Incremental update received. View now has " , chatView . count , " messages, view is on index " , modelIndex , ", own messages were read before index " , lastReadSentIndex ) ;
2020-09-01 23:37:48 +03:00
chatView . lastReadSentIndex = lastReadSentIndex ;
2020-12-28 19:12:21 +03:00
chatViewCooldownTimer . restart ( ) ;
2020-09-01 23:37:48 +03:00
}
Reduce ChatPage.qml jit compile time
First of all: Take all measurements I mention with a grain of salt – all of them are rough and not necessarily measured more than a few times. All times were measured on an Xperia X run via SDK.
Visiting a chat page can take a long time, especially before the qml is cached by the engine.
When opening it for the first time after application launch, it sometimes takes >1000ms from onClicked (OverviewPage) to Component.OnCompleted (Chatpage).
Subsequent activations take roughly 470-480ms.
With these changes, I was able to reduce these times to ~450ms for the first, ~100ms for subsequent activations of the ChatPage on my test device.
Things changed:
- The components for displaying extra content to a message are (mostly) gone and replaced by a single Loader. This Loader does not use sourceComponent to trade the initial compilation boost for a neglegible bit of runtime penalty.
- Connections were consolidated
- I was surprised how costly the inclusion of the RemorseItem was (compiling ~75ms, initializing up to ~20ms for every delegate). So I traded a bit for a compromise. deleteMessageRemorseItem is now defined on the appWindow level, where it gets a bit mitigated by the animations at application start. Also, only one deletion at a time is now possible. We can easily revert this change, but I thought it worthwhile despite its drawbacks.
- profileThumbnailComponent is now defined directly as sourceComponent, removing the need for its id. Probably didn't do anything.
- InReplyToRow had width: parent.width, so I removed horizontalCenter. Also probably didn't change compilation time at all.
- Another compromise I was willing to take – your opinion may differ: The PickerPages took ages (~200ms) to just parse/compile inside those Components, so I replaced them with the "string notation" of pageStack.push. Drawback: The first time a picker gets activated, you'll see how slow it is. Subsequent activations aren't that bad – also for the other pickers.
2020-10-30 22:36:32 +03:00
onNotificationSettingsUpdated: {
chatInformation = chatModel . getChatInformation ( ) ;
muteChatMenuItem . text = chatInformation . notification_settings . mute_for > 0 ? qsTr ( "Unmute Chat" ) : qsTr ( "Mute Chat" ) ;
}
2020-11-17 14:18:46 +03:00
onPinnedMessageChanged: {
chatInformation = chatModel . getChatInformation ( ) ;
if ( chatInformation . pinned_message_id . toString ( ) !== "0" ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Loading pinned message " , chatInformation . pinned_message_id ) ;
2020-11-17 14:18:46 +03:00
tdLibWrapper . getMessage ( chatInformation . id , chatInformation . pinned_message_id ) ;
} else {
pinnedMessageItem . pinnedMessage = undefined ;
}
}
2020-08-22 22:43:20 +03:00
}
2020-11-07 22:29:44 +03:00
Connections {
target: chatListModel
onChatJoined: {
2020-11-10 01:22:24 +03:00
appNotification . show ( qsTr ( "You joined the chat %1" ) . arg ( chatTitle ) ) ;
2020-11-07 22:29:44 +03:00
}
}
2020-10-18 19:57:01 +03:00
Timer {
id: lostFocusTimer
interval: 200
running: false
repeat: false
onTriggered: {
newMessageTextField . forceActiveFocus ( ) ;
}
}
Timer {
id: textReplacementTimer
interval: 600
running: false
repeat: false
onTriggered: {
handleMessageTextReplacement ( newMessageTextField . text , newMessageTextField . cursorPosition ) ;
}
}
2020-08-21 19:03:51 +03:00
Timer {
id: chatContactTimeUpdater
interval: 60000
2020-11-26 02:25:15 +03:00
running: isPrivateChat || isSecretChat
2020-08-21 19:03:51 +03:00
repeat: true
onTriggered: {
updateChatPartnerStatusText ( ) ;
}
}
2020-11-04 14:26:57 +03:00
Timer {
id: viewMessageTimer
2020-11-05 02:02:27 +03:00
interval: 1000
2020-11-04 14:26:57 +03:00
property int lastQueuedIndex: - 1
function queueViewMessage ( index ) {
2020-11-05 02:02:27 +03:00
if ( index > lastQueuedIndex ) {
2020-11-04 14:26:57 +03:00
lastQueuedIndex = index ;
2020-11-05 02:02:27 +03:00
start ( ) ;
2020-11-04 14:26:57 +03:00
}
}
onTriggered: {
if ( chatInformation . unread_count > 0 && lastQueuedIndex > - 1 ) {
var messageToRead = chatModel . getMessage ( lastQueuedIndex ) ;
2020-11-16 01:05:22 +03:00
if ( messageToRead && messageToRead . id ) {
2020-11-04 14:26:57 +03:00
tdLibWrapper . viewMessage ( chatInformation . id , messageToRead . id , false ) ;
}
lastQueuedIndex = - 1
}
2020-12-28 23:57:34 +03:00
if ( chatInformation . unread_count === 0 ) {
tdLibWrapper . readAllChatMentions ( chatInformation . id ) ;
}
2020-11-04 14:26:57 +03:00
}
}
2020-08-21 19:03:51 +03:00
2020-08-21 15:26:56 +03:00
SilicaFlickable {
id: chatContainer
2020-12-03 01:46:29 +03:00
onContentYChanged: {
// For some strange reason contentY sometimes is > 0 which doesn't make sense without a PushUpMenu (?)
// That leads to the problem that the whole flickable is moved slightly (or sometimes considerably) up
// which creates UX issues... As a workaround we are setting it to 0 in such cases.
// Better solutions are highly appreciated, contributions always welcome! ;)
if ( contentY > 0 ) {
contentY = 0 ;
}
}
2020-08-21 15:26:56 +03:00
anchors.fill: parent
Reduce ChatPage.qml jit compile time
First of all: Take all measurements I mention with a grain of salt – all of them are rough and not necessarily measured more than a few times. All times were measured on an Xperia X run via SDK.
Visiting a chat page can take a long time, especially before the qml is cached by the engine.
When opening it for the first time after application launch, it sometimes takes >1000ms from onClicked (OverviewPage) to Component.OnCompleted (Chatpage).
Subsequent activations take roughly 470-480ms.
With these changes, I was able to reduce these times to ~450ms for the first, ~100ms for subsequent activations of the ChatPage on my test device.
Things changed:
- The components for displaying extra content to a message are (mostly) gone and replaced by a single Loader. This Loader does not use sourceComponent to trade the initial compilation boost for a neglegible bit of runtime penalty.
- Connections were consolidated
- I was surprised how costly the inclusion of the RemorseItem was (compiling ~75ms, initializing up to ~20ms for every delegate). So I traded a bit for a compromise. deleteMessageRemorseItem is now defined on the appWindow level, where it gets a bit mitigated by the animations at application start. Also, only one deletion at a time is now possible. We can easily revert this change, but I thought it worthwhile despite its drawbacks.
- profileThumbnailComponent is now defined directly as sourceComponent, removing the need for its id. Probably didn't do anything.
- InReplyToRow had width: parent.width, so I removed horizontalCenter. Also probably didn't change compilation time at all.
- Another compromise I was willing to take – your opinion may differ: The PickerPages took ages (~200ms) to just parse/compile inside those Components, so I replaced them with the "string notation" of pageStack.push. Drawback: The first time a picker gets activated, you'll see how slow it is. Subsequent activations aren't that bad – also for the other pickers.
2020-10-30 22:36:32 +03:00
contentHeight: height
contentWidth: width
2020-08-21 15:26:56 +03:00
2020-09-16 21:43:36 +03:00
PullDownMenu {
2020-12-31 21:12:50 +03:00
visible: chatInformation . id !== chatPage . myUserId && ! stickerPickerLoader . active && ! voiceNoteOverlayLoader . active && ! messageOverlayLoader . active
2020-11-07 22:29:44 +03:00
MenuItem {
2020-11-26 18:11:12 +03:00
id: closeSecretChatMenuItem
visible: chatPage . isSecretChat && chatPage . secretChatDetails . state [ "@type" ] !== "secretChatStateClosed"
onClicked: {
2020-11-29 15:33:14 +03:00
var secretChatId = chatPage . secretChatDetails . id ;
Remorse . popupAction ( chatPage , qsTr ( "Closing chat" ) , function ( ) { tdLibWrapper . closeSecretChat ( secretChatId ) } ) ;
2020-11-26 18:11:12 +03:00
}
text: qsTr ( "Close Chat" )
}
2020-11-07 22:29:44 +03:00
MenuItem {
id: joinLeaveChatMenuItem
visible: ( chatPage . isSuperGroup || chatPage . isBasicGroup ) && chatGroupInformation && chatGroupInformation . status [ "@type" ] !== "chatMemberStatusBanned"
onClicked: {
if ( chatPage . userIsMember ) {
2020-11-29 15:33:14 +03:00
var chatId = chatInformation . id ;
Remorse . popupAction ( chatPage , qsTr ( "Leaving chat" ) , function ( ) {
tdLibWrapper . leaveChat ( chatId ) ;
// this does not care about the response (ideally type "ok" without further reference) for now
pageStack . pop ( pageStack . find ( function ( page ) { return ( page . _depth === 0 ) } ) ) ;
} ) ;
2020-11-07 22:29:44 +03:00
} else {
tdLibWrapper . joinChat ( chatInformation . id ) ;
}
}
text: chatPage . userIsMember ? qsTr ( "Leave Chat" ) : qsTr ( "Join Chat" )
}
2020-09-16 21:43:36 +03:00
MenuItem {
id: muteChatMenuItem
2020-11-07 22:29:44 +03:00
visible: chatPage . userIsMember
2020-09-16 21:43:36 +03:00
onClicked: {
var newNotificationSettings = chatInformation . notification_settings ;
if ( newNotificationSettings . mute_for > 0 ) {
newNotificationSettings . mute_for = 0 ;
} else {
newNotificationSettings . mute_for = 6666666 ;
}
2020-11-14 00:42:48 +03:00
newNotificationSettings . use_default_mute_for = false ;
2020-09-16 21:43:36 +03:00
tdLibWrapper . setChatNotificationSettings ( chatInformation . id , newNotificationSettings ) ;
}
text: chatInformation . notification_settings . mute_for > 0 ? qsTr ( "Unmute Chat" ) : qsTr ( "Mute Chat" )
}
2020-12-27 02:16:25 +03:00
MenuItem {
id: searchInChatMenuItem
2020-12-28 19:12:21 +03:00
visible: ! chatPage . isSecretChat && chatOverviewItem . visible
2020-12-27 02:16:25 +03:00
onClicked: {
// This automatically shows the search field as well
chatOverviewItem . visible = false ;
searchInChatField . focus = true ;
}
text: qsTr ( "Search in Chat" )
}
2020-09-16 21:43:36 +03:00
}
2020-10-19 13:20:02 +03:00
BackgroundItem {
id: headerMouseArea
height: headerRow . height
width: parent . width
onClicked: {
2020-12-06 17:50:03 +03:00
if ( chatPage . isSelecting ) {
2020-11-15 01:50:12 +03:00
chatPage . selectedMessages = [ ] ;
} else {
2020-11-16 23:38:55 +03:00
pageStack . navigateForward ( ) ;
2020-11-15 01:50:12 +03:00
}
2020-10-19 13:20:02 +03:00
}
}
2020-11-16 23:39:11 +03:00
2020-08-21 15:26:56 +03:00
Column {
id: chatColumn
width: parent . width
height: parent . height
Row {
id: headerRow
width: parent . width - ( 3 * Theme . horizontalPageMargin )
2020-11-22 02:39:49 +03:00
height: chatOverviewItem . height + ( chatPage . isPortrait ? ( 2 * Theme . paddingMedium ) : ( 2 * Theme . paddingSmall ) )
2020-08-21 15:26:56 +03:00
anchors.horizontalCenter: parent . horizontalCenter
spacing: Theme . paddingMedium
2020-11-25 02:23:38 +03:00
Item {
2020-11-22 02:39:49 +03:00
width: chatOverviewItem . height
height: chatOverviewItem . height
2020-09-19 19:49:11 +03:00
anchors.bottom: parent . bottom
2020-11-18 01:21:38 +03:00
anchors.bottomMargin: chatPage . isPortrait ? Theme.paddingMedium : Theme . paddingSmall
2020-11-19 22:25:53 +03:00
2020-11-25 02:23:38 +03:00
ProfileThumbnail {
id: chatPictureThumbnail
replacementStringHint: chatNameText . text
width: parent . height
height: parent . height
// Setting it directly may cause an stale state for the thumbnail in case the chat page
// was previously loaded with a picture and now it doesn't have one. Instead setting it
// when the ChatModel indicates a change. This also avoids flickering when the page is loaded...
Connections {
target: chatModel
onSmallPhotoChanged: {
chatPictureThumbnail . photoData = chatModel . smallPhoto ;
}
2020-11-19 22:25:53 +03:00
}
}
2020-11-25 02:23:38 +03:00
Rectangle {
id: chatSecretBackground
2021-01-06 16:01:55 +03:00
color: Theme . highlightBackgroundColor
2020-11-25 02:23:38 +03:00
width: chatPage . isPortrait ? Theme.fontSizeLarge : Theme . fontSizeMedium
height: width
anchors.left: parent . left
anchors.bottom: parent . bottom
radius: parent . width / 2
visible: chatPage . isSecretChat
}
Image {
source: "image://theme/icon-s-secure"
width: chatPage . isPortrait ? Theme.fontSizeSmall : Theme . fontSizeExtraSmall
height: width
anchors.centerIn: chatSecretBackground
visible: chatPage . isSecretChat
}
2020-08-21 15:26:56 +03:00
}
2020-11-22 02:39:49 +03:00
Item {
id: chatOverviewItem
2020-12-27 02:16:25 +03:00
opacity: visible ? 1 : 0
Behavior on opacity { FadeAnimation { } }
2020-08-21 15:26:56 +03:00
width: parent . width - chatPictureThumbnail . width - Theme . paddingMedium
2020-11-22 02:39:49 +03:00
height: chatNameText . height + chatStatusText . height
2020-09-19 19:49:11 +03:00
anchors.bottom: parent . bottom
2020-11-18 01:21:38 +03:00
anchors.bottomMargin: chatPage . isPortrait ? Theme.paddingMedium : Theme . paddingSmall
2020-11-22 02:39:49 +03:00
Label {
2020-08-21 15:26:56 +03:00
id: chatNameText
2020-11-22 02:39:49 +03:00
width: Math . min ( implicitWidth , parent . width )
anchors.right: parent . right
2020-08-21 15:26:56 +03:00
text: chatInformation . title !== "" ? Emoji . emojify ( chatInformation . title , font . pixelSize ) : qsTr ( "Unknown" )
textFormat: Text . StyledText
2020-11-18 01:21:38 +03:00
font.pixelSize: chatPage . isPortrait ? Theme.fontSizeLarge : Theme . fontSizeMedium
2020-08-21 15:26:56 +03:00
font.family: Theme . fontFamilyHeading
color: Theme . highlightColor
2020-11-22 02:39:49 +03:00
truncationMode: TruncationMode . Fade
2020-08-21 15:26:56 +03:00
maximumLineCount: 1
}
2020-11-22 02:39:49 +03:00
Label {
2020-08-21 15:26:56 +03:00
id: chatStatusText
2020-11-22 02:39:49 +03:00
width: Math . min ( implicitWidth , parent . width )
anchors {
right: parent . right
bottom: parent . bottom
}
2020-08-21 19:03:51 +03:00
text: ""
2020-08-21 15:26:56 +03:00
textFormat: Text . StyledText
2020-11-18 01:21:38 +03:00
font.pixelSize: chatPage . isPortrait ? Theme.fontSizeExtraSmall : Theme . fontSizeTiny
2020-08-21 15:26:56 +03:00
font.family: Theme . fontFamilyHeading
2020-10-19 13:20:02 +03:00
color: headerMouseArea . pressed ? Theme.secondaryHighlightColor : Theme . secondaryColor
2020-11-22 02:39:49 +03:00
truncationMode: TruncationMode . Fade
2020-08-21 15:26:56 +03:00
maximumLineCount: 1
}
}
2020-12-27 02:16:25 +03:00
Item {
id: searchInChatItem
visible: ! chatOverviewItem . visible
opacity: visible ? 1 : 0
Behavior on opacity { FadeAnimation { } }
width: parent . width - chatPictureThumbnail . width - Theme . paddingMedium
height: searchInChatField . height
anchors.bottom: parent . bottom
2020-12-28 19:12:21 +03:00
anchors.bottomMargin: chatPage . isPortrait ? Theme.paddingSmall : 0
2020-12-27 02:16:25 +03:00
SearchField {
id: searchInChatField
visible: false
width: visible ? parent.width : 0
placeholderText: qsTr ( "Search in chat..." )
active: searchInChatItem . visible
canHide: text === ""
onTextChanged: {
searchInChatTimer . restart ( ) ;
}
onHideClicked: {
resetFocus ( ) ;
}
EnterKey.iconSource: "image://theme/icon-m-enter-close"
EnterKey.onClicked: {
resetFocus ( ) ;
}
}
}
2020-08-21 15:26:56 +03:00
}
2020-11-16 23:39:11 +03:00
PinnedMessageItem {
id: pinnedMessageItem
2020-11-17 14:18:46 +03:00
onRequestShowMessage: {
messageOverlayLoader . overlayMessage = pinnedMessageItem . pinnedMessage ;
2020-11-18 16:22:08 +03:00
messageOverlayLoader . active = true ;
}
onRequestCloseMessage: {
messageOverlayLoader . overlayMessage = undefined ;
messageOverlayLoader . active = false ;
2020-11-17 14:18:46 +03:00
}
2020-11-16 23:39:11 +03:00
}
2020-08-29 19:04:23 +03:00
Item {
id: chatViewItem
2020-08-21 15:26:56 +03:00
width: parent . width
2020-11-18 16:22:08 +03:00
height: parent . height - headerRow . height - pinnedMessageItem . height - newMessageColumn . height - selectedMessagesActions . height
2020-08-21 15:26:56 +03:00
2020-09-18 23:55:59 +03:00
property int previousHeight ;
Component.onCompleted: {
previousHeight = height ;
}
onHeightChanged: {
var deltaHeight = previousHeight - height ;
chatView . contentY = chatView . contentY + deltaHeight ;
previousHeight = height ;
}
2020-09-22 22:05:24 +03:00
Timer {
id: chatViewCooldownTimer
interval: 2000
repeat: false
running: false
onTriggered: {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Cooldown completed..." ) ;
2020-09-22 22:05:24 +03:00
chatView . inCooldown = false ;
}
}
2020-11-18 16:22:08 +03:00
Loader {
asynchronous: true
active: chatView . blurred
anchors.fill: chatView
sourceComponent: Component {
FastBlur {
source: chatView
radius: Theme . paddingLarge
}
}
}
Reduce ChatPage.qml jit compile time
First of all: Take all measurements I mention with a grain of salt – all of them are rough and not necessarily measured more than a few times. All times were measured on an Xperia X run via SDK.
Visiting a chat page can take a long time, especially before the qml is cached by the engine.
When opening it for the first time after application launch, it sometimes takes >1000ms from onClicked (OverviewPage) to Component.OnCompleted (Chatpage).
Subsequent activations take roughly 470-480ms.
With these changes, I was able to reduce these times to ~450ms for the first, ~100ms for subsequent activations of the ChatPage on my test device.
Things changed:
- The components for displaying extra content to a message are (mostly) gone and replaced by a single Loader. This Loader does not use sourceComponent to trade the initial compilation boost for a neglegible bit of runtime penalty.
- Connections were consolidated
- I was surprised how costly the inclusion of the RemorseItem was (compiling ~75ms, initializing up to ~20ms for every delegate). So I traded a bit for a compromise. deleteMessageRemorseItem is now defined on the appWindow level, where it gets a bit mitigated by the animations at application start. Also, only one deletion at a time is now possible. We can easily revert this change, but I thought it worthwhile despite its drawbacks.
- profileThumbnailComponent is now defined directly as sourceComponent, removing the need for its id. Probably didn't do anything.
- InReplyToRow had width: parent.width, so I removed horizontalCenter. Also probably didn't change compilation time at all.
- Another compromise I was willing to take – your opinion may differ: The PickerPages took ages (~200ms) to just parse/compile inside those Components, so I replaced them with the "string notation" of pageStack.push. Drawback: The first time a picker gets activated, you'll see how slow it is. Subsequent activations aren't that bad – also for the other pickers.
2020-10-30 22:36:32 +03:00
2020-08-29 19:04:23 +03:00
SilicaListView {
id: chatView
2020-08-21 15:26:56 +03:00
2020-11-18 16:22:08 +03:00
visible: ! blurred
2020-12-31 21:12:50 +03:00
property bool blurred: messageOverlayLoader . item || stickerPickerLoader . item || voiceNoteOverlayLoader . item
2020-11-18 16:22:08 +03:00
2020-08-29 19:04:23 +03:00
anchors.fill: parent
opacity: chatPage . loading ? 0 : 1
2020-11-15 01:50:12 +03:00
Behavior on opacity { FadeAnimation { } }
2020-08-29 19:04:23 +03:00
clip: true
2020-11-02 22:54:18 +03:00
highlightMoveDuration: 0
highlightResizeDuration: 0
2020-08-30 20:04:16 +03:00
property int lastReadSentIndex: 0
2020-09-22 22:05:24 +03:00
property bool inCooldown: false
2020-11-02 22:54:18 +03:00
property bool manuallyScrolledToBottom
2020-11-07 22:58:23 +03:00
property QtObject precalculatedValues: QtObject {
2020-11-08 22:37:17 +03:00
readonly property alias page: chatPage
readonly property bool showUserInfo: page . isBasicGroup || ( page . isSuperGroup && ! page . isChannel )
readonly property int profileThumbnailDimensions: showUserInfo ? Theme.itemSizeSmall : 0
readonly property int pageMarginDouble: 2 * Theme . horizontalPageMargin
readonly property int paddingMediumDouble: 2 * Theme . paddingMedium
readonly property int entryWidth: chatView . width - pageMarginDouble
readonly property int textItemWidth: entryWidth - profileThumbnailDimensions - Theme . paddingSmall
readonly property int backgroundWidth: textItemWidth - pageMarginDouble
readonly property int backgroundRadius: textItemWidth / 50
readonly property int textColumnWidth: backgroundWidth - Theme . horizontalPageMargin
readonly property int messageInReplyToHeight: Theme . fontSizeExtraSmall * 2.571428571 + Theme . paddingSmall ;
readonly property int webPagePreviewHeight: ( ( textColumnWidth * 2 / 3 ) + ( 6 * Theme . fontSizeExtraSmall ) + ( 7 * Theme . paddingSmall ) )
2020-12-06 17:50:03 +03:00
readonly property bool pageIsSelecting: chatPage . isSelecting
2020-11-15 01:50:12 +03:00
2020-11-07 22:58:23 +03:00
}
2020-08-30 20:04:16 +03:00
2020-08-29 19:04:23 +03:00
function handleScrollPositionChanged ( ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "Current position: " , chatView . contentY ) ;
2020-12-28 19:12:21 +03:00
if ( chatOverviewItem . visible && chatInformation . unread_count > 0 ) {
2020-11-04 14:26:57 +03:00
var bottomIndex = chatView . indexAt ( chatView . contentX , ( chatView . contentY + chatView . height - Theme . horizontalPageMargin ) ) ;
2020-11-16 01:05:22 +03:00
if ( bottomIndex > - 1 ) {
2020-11-04 14:26:57 +03:00
viewMessageTimer . queueViewMessage ( bottomIndex )
2020-11-02 22:54:18 +03:00
}
2020-12-28 23:57:34 +03:00
} else {
tdLibWrapper . readAllChatMentions ( chatInformation . id ) ;
2020-11-02 22:54:18 +03:00
}
manuallyScrolledToBottom = chatView . atYEnd
}
function scrollToIndex ( index ) {
if ( index > 0 && index < chatView . count ) {
2020-11-07 22:58:23 +03:00
positionViewAtIndex ( index , ListView . Contain )
// currentIndex = index;
2020-11-02 22:54:18 +03:00
if ( index === chatView . count - 1 ) {
manuallyScrolledToBottom = true ;
}
}
2020-08-29 19:04:23 +03:00
}
2020-08-29 22:39:57 +03:00
onContentYChanged: {
2020-11-16 01:05:22 +03:00
if ( ! chatPage . loading && ! chatView . inCooldown ) {
if ( chatView . indexAt ( chatView . contentX , chatView . contentY ) < 10 ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Trying to get older history items..." ) ;
2020-11-16 01:05:22 +03:00
chatView . inCooldown = true ;
chatModel . triggerLoadMoreHistory ( ) ;
2020-12-28 19:12:21 +03:00
} else if ( chatOverviewItem . visible && chatView . indexAt ( chatView . contentX , chatView . contentY ) > ( count - 10 ) ) {
2020-11-20 23:58:26 +03:00
Debug . log ( "[ChatPage] Trying to get newer history items..." ) ;
2020-11-16 01:05:22 +03:00
chatView . inCooldown = true ;
chatModel . triggerLoadMoreFuture ( ) ;
}
2020-08-30 16:04:15 +03:00
}
2020-08-29 22:39:57 +03:00
}
2020-08-29 19:04:23 +03:00
onMovementEnded: {
2020-08-26 23:52:06 +03:00
handleScrollPositionChanged ( ) ;
2020-08-23 00:05:45 +03:00
}
2020-08-29 19:04:23 +03:00
onQuickScrollAnimatingChanged: {
if ( ! quickScrollAnimating ) {
handleScrollPositionChanged ( ) ;
2020-11-02 22:54:18 +03:00
if ( atYEnd ) { // handle some false guesses from quick scroll
chatView . scrollToIndex ( chatView . count - 2 )
chatView . scrollToIndex ( chatView . count - 1 )
}
2020-08-29 19:04:23 +03:00
}
}
model: chatModel
2020-11-08 22:37:17 +03:00
readonly property var contentComponentNames: ( {
Reduce ChatPage.qml jit compile time
First of all: Take all measurements I mention with a grain of salt – all of them are rough and not necessarily measured more than a few times. All times were measured on an Xperia X run via SDK.
Visiting a chat page can take a long time, especially before the qml is cached by the engine.
When opening it for the first time after application launch, it sometimes takes >1000ms from onClicked (OverviewPage) to Component.OnCompleted (Chatpage).
Subsequent activations take roughly 470-480ms.
With these changes, I was able to reduce these times to ~450ms for the first, ~100ms for subsequent activations of the ChatPage on my test device.
Things changed:
- The components for displaying extra content to a message are (mostly) gone and replaced by a single Loader. This Loader does not use sourceComponent to trade the initial compilation boost for a neglegible bit of runtime penalty.
- Connections were consolidated
- I was surprised how costly the inclusion of the RemorseItem was (compiling ~75ms, initializing up to ~20ms for every delegate). So I traded a bit for a compromise. deleteMessageRemorseItem is now defined on the appWindow level, where it gets a bit mitigated by the animations at application start. Also, only one deletion at a time is now possible. We can easily revert this change, but I thought it worthwhile despite its drawbacks.
- profileThumbnailComponent is now defined directly as sourceComponent, removing the need for its id. Probably didn't do anything.
- InReplyToRow had width: parent.width, so I removed horizontalCenter. Also probably didn't change compilation time at all.
- Another compromise I was willing to take – your opinion may differ: The PickerPages took ages (~200ms) to just parse/compile inside those Components, so I replaced them with the "string notation" of pageStack.push. Drawback: The first time a picker gets activated, you'll see how slow it is. Subsequent activations aren't that bad – also for the other pickers.
2020-10-30 22:36:32 +03:00
messageSticker: "StickerPreview" ,
messagePhoto: "ImagePreview" ,
messageVideo: "VideoPreview" ,
2020-11-14 22:02:34 +03:00
messageVideoNote: "VideoPreview" ,
Reduce ChatPage.qml jit compile time
First of all: Take all measurements I mention with a grain of salt – all of them are rough and not necessarily measured more than a few times. All times were measured on an Xperia X run via SDK.
Visiting a chat page can take a long time, especially before the qml is cached by the engine.
When opening it for the first time after application launch, it sometimes takes >1000ms from onClicked (OverviewPage) to Component.OnCompleted (Chatpage).
Subsequent activations take roughly 470-480ms.
With these changes, I was able to reduce these times to ~450ms for the first, ~100ms for subsequent activations of the ChatPage on my test device.
Things changed:
- The components for displaying extra content to a message are (mostly) gone and replaced by a single Loader. This Loader does not use sourceComponent to trade the initial compilation boost for a neglegible bit of runtime penalty.
- Connections were consolidated
- I was surprised how costly the inclusion of the RemorseItem was (compiling ~75ms, initializing up to ~20ms for every delegate). So I traded a bit for a compromise. deleteMessageRemorseItem is now defined on the appWindow level, where it gets a bit mitigated by the animations at application start. Also, only one deletion at a time is now possible. We can easily revert this change, but I thought it worthwhile despite its drawbacks.
- profileThumbnailComponent is now defined directly as sourceComponent, removing the need for its id. Probably didn't do anything.
- InReplyToRow had width: parent.width, so I removed horizontalCenter. Also probably didn't change compilation time at all.
- Another compromise I was willing to take – your opinion may differ: The PickerPages took ages (~200ms) to just parse/compile inside those Components, so I replaced them with the "string notation" of pageStack.push. Drawback: The first time a picker gets activated, you'll see how slow it is. Subsequent activations aren't that bad – also for the other pickers.
2020-10-30 22:36:32 +03:00
messageAnimation: "VideoPreview" ,
messageAudio: "AudioPreview" ,
messageVoiceNote: "AudioPreview" ,
messageDocument: "DocumentPreview" ,
messageLocation: "LocationPreview" ,
messageVenue: "LocationPreview" ,
messagePoll: "PollPreview"
} )
2020-10-31 22:02:18 +03:00
function getContentComponentHeight ( componentName , content , parentWidth ) {
switch ( componentName ) {
case "StickerPreview" : return content . sticker . height ;
case "ImagePreview" :
case "LocationPreview" :
return parentWidth * 0.66666666 ; // 2 / 3;
case "VideoPreview" :
2020-11-15 00:53:51 +03:00
return ( content [ '@type' ] === "messageVideoNote" ) ? parentWidth : ( Functions . getVideoHeight ( parentWidth , ( content [ '@type' ] === "messageVideo" ) ? content.video : content . animation ) ) ;
2020-10-31 22:02:18 +03:00
case "AudioPreview" :
return parentWidth / 2 ;
case "DocumentPreview" :
return Theme . itemSizeSmall ;
case "PollPreview" :
return Theme . itemSizeSmall * ( 4 + content . poll . options ) ;
}
}
2020-08-21 15:26:56 +03:00
2020-11-14 22:02:34 +03:00
readonly property var simpleDelegateMessages: [ "messageBasicGroupChatCreate" ,
"messageChatAddMembers" ,
"messageChatChangePhoto" ,
"messageChatChangeTitle" ,
"messageChatDeleteMember" ,
"messageChatDeletePhoto" ,
"messageChatJoinByLink" ,
"messageChatSetTtl" ,
"messageChatUpgradeFrom" ,
"messageChatUpgradeTo" ,
"messageCustomServiceAction" ,
"messagePinMessage" ,
"messageScreenshotTaken" ,
"messageSupergroupChatCreate" ,
"messageUnsupported" ]
2020-11-05 16:05:33 +03:00
delegate: Loader {
width: chatView . width
Component {
id: messageListViewItemComponent
2020-11-07 22:58:23 +03:00
MessageListViewItem {
precalculatedValues: chatView . precalculatedValues
2020-12-06 07:38:03 +03:00
chatId: chatModel . chatId
myMessage: model . display
messageId: model . message_id
2021-01-08 05:14:31 +03:00
messageIndex: model . index
2020-12-27 02:01:59 +03:00
extraContentComponentName: chatView . contentComponentNames [ model . content_type ] || ""
2020-12-15 21:15:12 +03:00
canReplyToMessage: chatPage . canSendMessages
onReplyToMessage: {
newMessageInReplyToRow . inReplyToMessage = myMessage
newMessageTextField . focus = true
}
onEditMessage: {
newMessageColumn . editMessageId = messageId
2020-12-29 18:32:39 +03:00
newMessageTextField . text = Functions . getMessageText ( myMessage , false , chatPage . myUserId , true )
2020-12-15 21:15:12 +03:00
newMessageTextField . focus = true
}
2020-11-07 22:58:23 +03:00
}
2020-09-15 23:47:39 +03:00
}
2020-11-05 16:05:33 +03:00
Component {
id: messageListViewItemSimpleComponent
MessageListViewItemSimple { }
2020-08-22 22:43:20 +03:00
}
2020-12-06 07:38:03 +03:00
sourceComponent: chatView . simpleDelegateMessages . indexOf ( model . content_type ) > - 1 ? messageListViewItemSimpleComponent : messageListViewItemComponent
2020-08-22 22:43:20 +03:00
}
2020-08-29 19:04:23 +03:00
VerticalScrollDecorator { }
2020-10-19 13:20:02 +03:00
ViewPlaceholder {
2020-11-26 02:25:15 +03:00
id: chatViewPlaceholder
2020-10-19 13:20:02 +03:00
enabled: chatView . count === 0
2020-11-26 02:25:15 +03:00
text: ( chatPage . isSecretChat && ! chatPage . isSecretChatReady ) ? qsTr ( "This secret chat is not yet ready. Your chat partner needs to go online first." ) : qsTr ( "This chat is empty." )
2020-10-19 13:20:02 +03:00
}
2020-08-29 19:04:23 +03:00
}
Column {
width: parent . width
height: loadingLabel . height + loadingBusyIndicator . height + Theme . paddingMedium
spacing: Theme . paddingMedium
anchors.verticalCenter: parent . verticalCenter
opacity: chatPage . loading ? 1 : 0
Reduce ChatPage.qml jit compile time
First of all: Take all measurements I mention with a grain of salt – all of them are rough and not necessarily measured more than a few times. All times were measured on an Xperia X run via SDK.
Visiting a chat page can take a long time, especially before the qml is cached by the engine.
When opening it for the first time after application launch, it sometimes takes >1000ms from onClicked (OverviewPage) to Component.OnCompleted (Chatpage).
Subsequent activations take roughly 470-480ms.
With these changes, I was able to reduce these times to ~450ms for the first, ~100ms for subsequent activations of the ChatPage on my test device.
Things changed:
- The components for displaying extra content to a message are (mostly) gone and replaced by a single Loader. This Loader does not use sourceComponent to trade the initial compilation boost for a neglegible bit of runtime penalty.
- Connections were consolidated
- I was surprised how costly the inclusion of the RemorseItem was (compiling ~75ms, initializing up to ~20ms for every delegate). So I traded a bit for a compromise. deleteMessageRemorseItem is now defined on the appWindow level, where it gets a bit mitigated by the animations at application start. Also, only one deletion at a time is now possible. We can easily revert this change, but I thought it worthwhile despite its drawbacks.
- profileThumbnailComponent is now defined directly as sourceComponent, removing the need for its id. Probably didn't do anything.
- InReplyToRow had width: parent.width, so I removed horizontalCenter. Also probably didn't change compilation time at all.
- Another compromise I was willing to take – your opinion may differ: The PickerPages took ages (~200ms) to just parse/compile inside those Components, so I replaced them with the "string notation" of pageStack.push. Drawback: The first time a picker gets activated, you'll see how slow it is. Subsequent activations aren't that bad – also for the other pickers.
2020-10-30 22:36:32 +03:00
Behavior on opacity { FadeAnimation { } }
2020-08-29 19:04:23 +03:00
visible: chatPage . loading
InfoLabel {
id: loadingLabel
text: qsTr ( "Loading messages..." )
}
BusyIndicator {
id: loadingBusyIndicator
anchors.horizontalCenter: parent . horizontalCenter
running: chatPage . loading
size: BusyIndicatorSize . Large
}
2020-08-21 15:26:56 +03:00
}
2020-09-22 20:26:49 +03:00
Item {
id: chatUnreadMessagesItem
2020-08-29 22:39:57 +03:00
width: Theme . fontSizeHuge
height: Theme . fontSizeHuge
anchors.right: parent . right
anchors.rightMargin: Theme . paddingMedium
anchors.bottom: parent . bottom
anchors.bottomMargin: Theme . paddingMedium
2020-12-28 19:12:21 +03:00
visible: ! chatPage . loading && chatInformation . unread_count > 0 && chatOverviewItem . visible
2020-09-22 20:26:49 +03:00
Rectangle {
id: chatUnreadMessagesCountBackground
color: Theme . highlightBackgroundColor
anchors.fill: parent
radius: width / 2
2020-12-28 19:12:21 +03:00
visible: chatUnreadMessagesItem . visible
2020-09-22 20:26:49 +03:00
}
2020-08-29 22:39:57 +03:00
2020-09-22 20:26:49 +03:00
Text {
id: chatUnreadMessagesCount
font.pixelSize: Theme . fontSizeMedium
font.bold: true
color: Theme . primaryColor
anchors.centerIn: chatUnreadMessagesCountBackground
2020-12-28 19:12:21 +03:00
visible: chatUnreadMessagesItem . visible
2020-09-22 20:26:49 +03:00
text: chatInformation . unread_count > 99 ? "99+" : chatInformation . unread_count
}
MouseArea {
anchors.fill: parent
onClicked: {
2020-11-02 22:54:18 +03:00
chatView . scrollToIndex ( chatView . count - 1 - chatInformation . unread_count )
2020-09-22 20:26:49 +03:00
}
}
2020-08-29 22:39:57 +03:00
}
2020-10-12 23:44:21 +03:00
2020-10-16 00:43:55 +03:00
Loader {
id: stickerPickerLoader
active: false
asynchronous: true
2020-10-16 01:02:58 +03:00
width: parent . width
height: active ? parent.height : 0
2020-10-16 00:43:55 +03:00
source: "../components/StickerPicker.qml"
}
2020-11-29 07:28:31 +03:00
Connections {
target: stickerPickerLoader . item
onStickerPicked: {
2020-11-29 14:34:42 +03:00
Debug . log ( "Sticker picked: " + stickerId ) ;
tdLibWrapper . sendStickerMessage ( chatInformation . id , stickerId ) ;
stickerPickerLoader . active = false ;
2021-01-02 20:08:33 +03:00
attachmentOptionsFlickable . isNeeded = false ;
2020-11-29 07:28:31 +03:00
}
}
2020-11-17 14:18:46 +03:00
Loader {
id: messageOverlayLoader
property var overlayMessage ;
active: false
asynchronous: true
width: parent . width
height: active ? parent.height : 0
sourceComponent: Component {
MessageOverlayFlickable {
overlayMessage: messageOverlayLoader . overlayMessage
showHeader: ! chatPage . isChannel
onRequestClose: {
messageOverlayLoader . active = false ;
}
}
}
}
2020-12-31 21:12:50 +03:00
Loader {
id: voiceNoteOverlayLoader
active: false
asynchronous: true
width: parent . width
height: active ? parent.height : 0
source: "../components/VoiceNoteOverlay.qml"
2021-01-02 02:15:25 +03:00
onActiveChanged: {
if ( ! active ) {
fernschreiberUtils . stopRecordingVoiceNote ( ) ;
}
}
2020-12-31 21:12:50 +03:00
}
2020-08-21 15:26:56 +03:00
}
2020-08-28 18:40:25 +03:00
Column {
id: newMessageColumn
2020-09-19 19:49:11 +03:00
spacing: Theme . paddingSmall
2020-11-15 01:50:12 +03:00
topPadding: Theme . paddingSmall
2020-08-28 18:40:25 +03:00
anchors.horizontalCenter: parent . horizontalCenter
2020-12-06 17:50:03 +03:00
visible: height > 0
width: parent . width - ( 2 * Theme . horizontalPageMargin )
height: isNeeded ? implicitHeight : 0
Behavior on height { SmoothedAnimation { duration: 200 } }
2020-08-28 18:40:25 +03:00
2020-12-15 21:15:12 +03:00
readonly property bool isNeeded: ! chatPage . isSelecting && chatPage . canSendMessages
2020-08-29 14:38:28 +03:00
property string replyToMessageId: "0" ;
2020-09-19 00:43:23 +03:00
property string editMessageId: "0" ;
2020-08-29 14:38:28 +03:00
2020-08-28 18:40:25 +03:00
InReplyToRow {
2020-08-29 12:22:18 +03:00
onInReplyToMessageChanged: {
2020-08-29 17:58:48 +03:00
if ( inReplyToMessage ) {
2020-08-29 14:38:28 +03:00
newMessageColumn . replyToMessageId = newMessageInReplyToRow . inReplyToMessage . id . toString ( )
newMessageInReplyToRow . visible = true ;
2020-08-29 12:22:18 +03:00
} else {
2020-08-29 14:38:28 +03:00
newMessageInReplyToRow . visible = false ;
newMessageColumn . replyToMessageId = "0" ;
2020-08-29 12:22:18 +03:00
}
}
2020-10-18 22:54:15 +03:00
editable: true
onClearRequested: {
newMessageInReplyToRow . inReplyToMessage = null ;
}
2020-08-29 12:22:18 +03:00
id: newMessageInReplyToRow
myUserId: chatPage . myUserId
visible: false
2020-08-21 15:26:56 +03:00
}
2021-01-02 20:08:33 +03:00
Flickable {
id: attachmentOptionsFlickable
2020-11-29 07:28:31 +03:00
property bool isNeeded: false
2021-01-04 00:32:26 +03:00
width: chatPage . width
x: - Theme . horizontalPageMargin
2021-01-02 20:08:33 +03:00
height: isNeeded ? attachmentOptionsRow.height : 0
2020-11-29 07:28:31 +03:00
Behavior on height { SmoothedAnimation { duration: 200 } }
2021-01-02 20:08:33 +03:00
visible: height > 0
contentHeight: attachmentOptionsRow . height
2021-01-04 00:32:26 +03:00
contentWidth: Math . max ( width , attachmentOptionsRow . width )
property bool fadeRight: ( attachmentOptionsRow . width - contentX ) > width
property bool fadeLeft: ! fadeRight && contentX > 0
layer.enabled: fadeRight || fadeLeft
layer.effect: OpacityRampEffectBase {
direction: attachmentOptionsFlickable . fadeRight ? OpacityRamp.LeftToRight : OpacityRamp . RightToLeft
source: attachmentOptionsFlickable
slope: 1 + 6 * ( chatPage . width ) / Screen . width
offset: 1 - 1 / slope
}
2021-01-02 20:08:33 +03:00
Row {
id: attachmentOptionsRow
height: attachImageIconButton . height
anchors.right: parent . right
layoutDirection: Qt . RightToLeft
spacing: Theme . paddingMedium
2021-01-04 00:32:26 +03:00
leftPadding: Theme . horizontalPageMargin
rightPadding: Theme . horizontalPageMargin
2021-01-02 20:08:33 +03:00
IconButton {
id: attachImageIconButton
visible: chatPage . hasSendPrivilege ( "can_send_media_messages" )
icon.source: "image://theme/icon-m-image"
onClicked: {
var picker = pageStack . push ( "Sailfish.Pickers.ImagePickerPage" , {
allowedOrientations: chatPage . allowedOrientations
} )
picker . selectedContentPropertiesChanged . connect ( function ( ) {
attachmentOptionsFlickable . isNeeded = false ;
Debug . log ( "Selected document: " , picker . selectedContentProperties . filePath ) ;
attachmentPreviewRow . fileProperties = picker . selectedContentProperties ;
attachmentPreviewRow . isPicture = true ;
attachmentPreviewRow . visible = true ;
controlSendButton ( ) ;
} )
}
2020-12-31 21:12:50 +03:00
}
2021-01-02 20:08:33 +03:00
IconButton {
visible: chatPage . hasSendPrivilege ( "can_send_media_messages" )
icon.source: "image://theme/icon-m-video"
onClicked: {
var picker = pageStack . push ( "Sailfish.Pickers.VideoPickerPage" , {
allowedOrientations: chatPage . allowedOrientations
} )
picker . selectedContentPropertiesChanged . connect ( function ( ) {
attachmentOptionsFlickable . isNeeded = false ;
Debug . log ( "Selected video: " , picker . selectedContentProperties . filePath ) ;
attachmentPreviewRow . fileProperties = picker . selectedContentProperties ;
attachmentPreviewRow . isVideo = true ;
attachmentPreviewRow . visible = true ;
controlSendButton ( ) ;
} )
}
2020-12-31 21:12:50 +03:00
}
2021-01-02 20:08:33 +03:00
IconButton {
visible: chatPage . hasSendPrivilege ( "can_send_media_messages" )
icon.source: "image://theme/icon-m-mic"
icon . sourceSize {
width: Theme . iconSizeMedium
height: Theme . iconSizeMedium
}
highlighted: down || voiceNoteOverlayLoader . active
onClicked: {
voiceNoteOverlayLoader . active = ! voiceNoteOverlayLoader . active ;
stickerPickerLoader . active = false ;
}
2020-09-27 14:49:06 +03:00
}
2021-01-02 20:08:33 +03:00
IconButton {
visible: chatPage . hasSendPrivilege ( "can_send_media_messages" )
icon.source: "image://theme/icon-m-document"
onClicked: {
var picker = pageStack . push ( "Sailfish.Pickers.FilePickerPage" , {
allowedOrientations: chatPage . allowedOrientations
} )
picker . selectedContentPropertiesChanged . connect ( function ( ) {
attachmentOptionsFlickable . isNeeded = false ;
Debug . log ( "Selected document: " , picker . selectedContentProperties . filePath ) ;
attachmentPreviewRow . fileProperties = picker . selectedContentProperties ;
attachmentPreviewRow . isDocument = true ;
attachmentPreviewRow . visible = true ;
controlSendButton ( ) ;
} )
}
2020-10-26 17:15:53 +03:00
}
2021-01-02 20:08:33 +03:00
IconButton {
visible: chatPage . hasSendPrivilege ( "can_send_other_messages" )
icon.source: "../../images/icon-m-sticker.svg"
icon . sourceSize {
width: Theme . iconSizeMedium
height: Theme . iconSizeMedium
}
highlighted: down || stickerPickerLoader . active
onClicked: {
stickerPickerLoader . active = ! stickerPickerLoader . active ;
voiceNoteOverlayLoader . active = false ;
}
2020-10-04 16:09:06 +03:00
}
2021-01-02 20:08:33 +03:00
IconButton {
visible: ! ( chatPage . isPrivateChat || chatPage . isSecretChat ) && chatPage . hasSendPrivilege ( "can_send_polls" )
icon.source: "image://theme/icon-m-question"
onClicked: {
pageStack . push ( Qt . resolvedUrl ( "../pages/PollCreationPage.qml" ) , { "chatId" : chatInformation . id , groupName: chatInformation . title } ) ;
attachmentOptionsFlickable . isNeeded = false ;
}
2020-10-23 11:29:50 +03:00
}
2021-01-03 03:22:30 +03:00
IconButton {
visible: fernschreiberUtils . supportsGeoLocation ( ) && newMessageTextField . text === ""
icon.source: "image://theme/icon-m-location"
icon . sourceSize {
width: Theme . iconSizeMedium
height: Theme . iconSizeMedium
}
onClicked: {
fernschreiberUtils . startGeoLocationUpdates ( ) ;
attachmentOptionsFlickable . isNeeded = false ;
attachmentPreviewRow . isLocation = true ;
attachmentPreviewRow . attachmentDescription = qsTr ( "Location: Obtaining position..." ) ;
attachmentPreviewRow . visible = true ;
controlSendButton ( ) ;
}
}
2020-10-23 11:29:50 +03:00
}
2021-01-02 20:08:33 +03:00
2020-09-27 14:49:06 +03:00
}
2021-01-02 20:08:33 +03:00
2020-09-27 14:49:06 +03:00
Row {
id: attachmentPreviewRow
visible: false
spacing: Theme . paddingMedium
2020-09-29 00:08:22 +03:00
width: parent . width
layoutDirection: Qt . RightToLeft
2020-09-27 14:49:06 +03:00
anchors.right: parent . right
property bool isPicture: false ;
2020-09-28 00:24:22 +03:00
property bool isVideo: false ;
property bool isDocument: false ;
2021-01-02 19:22:09 +03:00
property bool isVoiceNote: false ;
2021-01-03 03:22:30 +03:00
property bool isLocation: false ;
property var locationData: ( { } ) ;
2020-11-06 01:23:37 +03:00
property var fileProperties: ( { } ) ;
2021-01-02 19:22:09 +03:00
property string attachmentDescription: "" ;
2020-09-29 00:08:22 +03:00
2021-01-03 03:22:30 +03:00
Connections {
target: fernschreiberUtils
onNewPositionInformation: {
attachmentPreviewRow . locationData = positionInformation ;
if ( attachmentPreviewRow . isLocation ) {
attachmentPreviewRow . attachmentDescription = qsTr ( "Location (%1/%2)" ) . arg ( attachmentPreviewRow . locationData . latitude ) . arg ( attachmentPreviewRow . locationData . longitude ) ;
}
}
}
2020-09-29 00:08:22 +03:00
IconButton {
id: removeAttachmentsIconButton
icon.source: "image://theme/icon-m-clear"
onClicked: {
clearAttachmentPreviewRow ( ) ;
2020-09-29 22:00:23 +03:00
controlSendButton ( ) ;
2020-09-29 00:08:22 +03:00
}
}
2020-09-27 14:49:06 +03:00
2020-09-28 00:24:22 +03:00
Thumbnail {
2020-09-27 14:49:06 +03:00
id: attachmentPreviewImage
width: Theme . itemSizeMedium
height: Theme . itemSizeMedium
2020-09-28 00:24:22 +03:00
sourceSize.width: width
sourceSize.height: height
2020-09-27 14:49:06 +03:00
2020-09-28 00:24:22 +03:00
fillMode: Thumbnail . PreserveAspectCrop
Reduce ChatPage.qml jit compile time
First of all: Take all measurements I mention with a grain of salt – all of them are rough and not necessarily measured more than a few times. All times were measured on an Xperia X run via SDK.
Visiting a chat page can take a long time, especially before the qml is cached by the engine.
When opening it for the first time after application launch, it sometimes takes >1000ms from onClicked (OverviewPage) to Component.OnCompleted (Chatpage).
Subsequent activations take roughly 470-480ms.
With these changes, I was able to reduce these times to ~450ms for the first, ~100ms for subsequent activations of the ChatPage on my test device.
Things changed:
- The components for displaying extra content to a message are (mostly) gone and replaced by a single Loader. This Loader does not use sourceComponent to trade the initial compilation boost for a neglegible bit of runtime penalty.
- Connections were consolidated
- I was surprised how costly the inclusion of the RemorseItem was (compiling ~75ms, initializing up to ~20ms for every delegate). So I traded a bit for a compromise. deleteMessageRemorseItem is now defined on the appWindow level, where it gets a bit mitigated by the animations at application start. Also, only one deletion at a time is now possible. We can easily revert this change, but I thought it worthwhile despite its drawbacks.
- profileThumbnailComponent is now defined directly as sourceComponent, removing the need for its id. Probably didn't do anything.
- InReplyToRow had width: parent.width, so I removed horizontalCenter. Also probably didn't change compilation time at all.
- Another compromise I was willing to take – your opinion may differ: The PickerPages took ages (~200ms) to just parse/compile inside those Components, so I replaced them with the "string notation" of pageStack.push. Drawback: The first time a picker gets activated, you'll see how slow it is. Subsequent activations aren't that bad – also for the other pickers.
2020-10-30 22:36:32 +03:00
mimeType: typeof attachmentPreviewRow . fileProperties !== "undefined" ? attachmentPreviewRow . fileProperties . mimeType || "" : ""
source: typeof attachmentPreviewRow . fileProperties !== "undefined" ? attachmentPreviewRow . fileProperties . url || "" : ""
2020-09-28 00:24:22 +03:00
visible: attachmentPreviewRow . isPicture || attachmentPreviewRow . isVideo
}
2020-11-22 02:39:49 +03:00
Label {
2020-09-28 00:24:22 +03:00
id: attachmentPreviewText
font.pixelSize: Theme . fontSizeSmall
2021-01-03 03:22:30 +03:00
text: ( attachmentPreviewRow . isVoiceNote || attachmentPreviewRow . isLocation ) ? attachmentPreviewRow.attachmentDescription : ( typeof attachmentPreviewRow . fileProperties !== "undefined" ? attachmentPreviewRow . fileProperties . fileName || "" : "" ) ;
2020-09-29 00:08:22 +03:00
anchors.verticalCenter: parent . verticalCenter
2020-09-28 00:24:22 +03:00
maximumLineCount: 1
2020-11-22 02:39:49 +03:00
truncationMode: TruncationMode . Fade
2020-09-28 00:24:22 +03:00
color: Theme . secondaryColor
2021-01-03 03:22:30 +03:00
visible: attachmentPreviewRow . isDocument || attachmentPreviewRow . isVoiceNote || attachmentPreviewRow . isLocation
2020-09-27 14:49:06 +03:00
}
}
2020-09-30 00:37:56 +03:00
Row {
id: uploadStatusRow
visible: false
spacing: Theme . paddingMedium
width: parent . width
anchors.right: parent . right
Text {
id: uploadingText
font.pixelSize: Theme . fontSizeSmall
text: qsTr ( "Uploading..." )
anchors.verticalCenter: parent . verticalCenter
color: Theme . secondaryColor
visible: uploadStatusRow . visible
}
ProgressBar {
id: uploadingProgressBar
minimumValue: 0
maximumValue: 100
value: 0
visible: uploadStatusRow . visible
width: parent . width - uploadingText . width - Theme . paddingMedium
}
}
2020-10-18 19:57:01 +03:00
Column {
id: emojiColumn
width: parent . width
anchors.horizontalCenter: parent . horizontalCenter
visible: emojiProposals ? ( emojiProposals . length > 0 ? true : false ) : false
opacity: emojiProposals ? ( emojiProposals . length > 0 ? 1 : 0 ) : 0
Behavior on opacity { NumberAnimation { } }
spacing: Theme . paddingMedium
Flickable {
width: parent . width
height: emojiResultRow . height + Theme . paddingSmall
anchors.horizontalCenter: parent . horizontalCenter
contentWidth: emojiResultRow . width
clip: true
Row {
id: emojiResultRow
spacing: Theme . paddingMedium
Repeater {
model: emojiProposals
Item {
height: singleEmojiRow . height
width: singleEmojiRow . width
Row {
id: singleEmojiRow
spacing: Theme . paddingSmall
Image {
id: emojiPicture
source: "../js/emoji/" + modelData . file_name
width: Theme . fontSizeLarge
height: Theme . fontSizeLarge
}
}
MouseArea {
anchors.fill: parent
onClicked: {
replaceMessageText ( newMessageTextField . text , newMessageTextField . cursorPosition , modelData . emoji ) ;
emojiProposals = null ;
}
}
}
}
}
}
}
2020-11-29 01:00:10 +03:00
Column {
id: atMentionColumn
width: parent . width
anchors.horizontalCenter: parent . horizontalCenter
visible: knownUsersRepeater . count > 0 ? true : false
opacity: knownUsersRepeater . count > 0 ? 1 : 0
Behavior on opacity { NumberAnimation { } }
spacing: Theme . paddingMedium
Flickable {
width: parent . width
height: atMentionResultRow . height + Theme . paddingSmall
anchors.horizontalCenter: parent . horizontalCenter
contentWidth: atMentionResultRow . width
clip: true
Row {
id: atMentionResultRow
spacing: Theme . paddingMedium
Repeater {
id: knownUsersRepeater
Item {
id: knownUserItem
height: singleAtMentionRow . height
width: singleAtMentionRow . width
property string atMentionText: "@" + ( user_name ? user_name : user_id + "(" + title + ")" ) ;
Row {
id: singleAtMentionRow
spacing: Theme . paddingSmall
Item {
width: Theme . fontSizeHuge
height: Theme . fontSizeHuge
anchors.verticalCenter: parent . verticalCenter
ProfileThumbnail {
id: atMentionThumbnail
replacementStringHint: title
width: parent . width
height: parent . width
photoData: photo_small
}
}
Column {
Text {
text: Emoji . emojify ( title , Theme . fontSizeExtraSmall )
textFormat: Text . StyledText
color: Theme . primaryColor
font.pixelSize: Theme . fontSizeExtraSmall
font.bold: true
}
Text {
id: userHandleText
text: user_handle
textFormat: Text . StyledText
color: Theme . primaryColor
font.pixelSize: Theme . fontSizeExtraSmall
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
replaceMessageText ( newMessageTextField . text , newMessageTextField . cursorPosition , knownUserItem . atMentionText ) ;
knownUsersRepeater . model = undefined ;
}
}
}
}
}
}
}
2020-10-18 22:54:15 +03:00
Row {
2020-09-19 00:43:23 +03:00
width: parent . width
2020-10-18 22:54:15 +03:00
spacing: Theme . paddingSmall
2020-09-19 00:43:23 +03:00
visible: newMessageColumn . editMessageId !== "0"
2020-10-18 22:54:15 +03:00
Text {
width: parent . width - Theme . paddingSmall - removeEditMessageIconButton . width
anchors.verticalCenter: parent . verticalCenter
id: editMessageText
font.pixelSize: Theme . fontSizeSmall
font.bold: true
text: qsTr ( "Edit Message" )
color: Theme . secondaryColor
}
IconButton {
id: removeEditMessageIconButton
icon.source: "image://theme/icon-m-clear"
onClicked: {
newMessageColumn . editMessageId = "0" ;
newMessageTextField . text = "" ;
}
}
2020-09-19 00:43:23 +03:00
}
2020-08-28 18:40:25 +03:00
Row {
id: newMessageRow
width: parent . width
anchors.horizontalCenter: parent . horizontalCenter
2020-09-24 00:41:17 +03:00
TextArea {
id: newMessageTextField
2020-12-30 18:18:50 +03:00
width: parent . width - attachmentIconButton . width - ( newMessageSendButton . visible ? newMessageSendButton.width : 0 )
2020-09-24 00:41:17 +03:00
height: Math . min ( chatContainer . height / 3 , implicitHeight )
2020-08-28 18:40:25 +03:00
anchors.verticalCenter: parent . verticalCenter
2020-09-24 00:41:17 +03:00
font.pixelSize: Theme . fontSizeSmall
placeholderText: qsTr ( "Your message" )
labelVisible: false
textLeftMargin: 0
textTopMargin: 0
2021-01-03 03:22:30 +03:00
enabled: ! attachmentPreviewRow . isLocation
2020-09-24 00:41:17 +03:00
EnterKey.onClicked: {
2020-10-06 04:40:12 +03:00
if ( appSettings . sendByEnter ) {
2020-09-27 14:49:06 +03:00
sendMessage ( ) ;
2020-09-24 00:41:17 +03:00
newMessageTextField . text = "" ;
2021-01-01 03:30:23 +03:00
if ( ! appSettings . focusTextAreaAfterSend ) {
newMessageTextField . focus = false ;
}
2020-09-24 00:41:17 +03:00
}
}
2020-10-06 04:40:12 +03:00
EnterKey.enabled: ! appSettings . sendByEnter || text . length
EnterKey.iconSource: appSettings . sendByEnter ? "image://theme/icon-m-chat" : "image://theme/icon-m-enter"
2020-09-16 23:04:02 +03:00
2020-09-24 00:41:17 +03:00
onTextChanged: {
2020-09-29 22:00:23 +03:00
controlSendButton ( ) ;
2020-10-18 19:57:01 +03:00
textReplacementTimer . restart ( ) ;
2020-08-28 18:40:25 +03:00
}
}
2020-09-24 00:41:17 +03:00
IconButton {
id: attachmentIconButton
2021-01-02 20:08:33 +03:00
icon.source: "image://theme/icon-m-attach?" + ( attachmentOptionsFlickable . isNeeded ? Theme.highlightColor : Theme . primaryColor )
2020-09-24 23:27:17 +03:00
anchors.bottom: parent . bottom
anchors.bottomMargin: Theme . paddingSmall
2020-10-16 00:43:55 +03:00
enabled: ! attachmentPreviewRow . visible
2020-09-24 00:41:17 +03:00
onClicked: {
2021-01-02 20:08:33 +03:00
if ( attachmentOptionsFlickable . isNeeded ) {
attachmentOptionsFlickable . isNeeded = false ;
2020-10-16 00:43:55 +03:00
stickerPickerLoader . active = false ;
2020-12-31 21:12:50 +03:00
voiceNoteOverlayLoader . active = false ;
2020-09-27 14:49:06 +03:00
} else {
2021-01-02 20:08:33 +03:00
attachmentOptionsFlickable . isNeeded = true ;
2020-09-27 14:49:06 +03:00
}
2020-09-24 00:41:17 +03:00
}
}
IconButton {
id: newMessageSendButton
icon.source: "image://theme/icon-m-chat"
2020-09-24 23:27:17 +03:00
anchors.bottom: parent . bottom
anchors.bottomMargin: Theme . paddingSmall
2020-12-30 18:18:50 +03:00
visible: ! appSettings . sendByEnter || attachmentPreviewRow . visible
2020-09-24 00:41:17 +03:00
enabled: false
onClicked: {
2020-09-27 14:49:06 +03:00
sendMessage ( ) ;
2020-09-24 00:41:17 +03:00
newMessageTextField . text = "" ;
2021-01-01 03:30:23 +03:00
if ( ! appSettings . focusTextAreaAfterSend ) {
newMessageTextField . focus = false ;
}
2020-08-21 15:26:56 +03:00
}
}
}
}
2020-12-06 17:50:03 +03:00
}
}
2020-11-18 16:22:08 +03:00
2020-12-06 17:50:03 +03:00
Loader {
id: selectedMessagesActions
asynchronous: true
anchors.bottom: parent . bottom
readonly property bool isNeeded: chatPage . isSelecting
active: height > 0
width: parent . width
height: isNeeded ? Theme.itemSizeMedium : 0
Behavior on height { SmoothedAnimation { duration: 200 } }
sourceComponent: Component {
Item {
clip: true
2020-11-15 01:50:12 +03:00
2020-12-06 17:50:03 +03:00
IconButton {
anchors {
left: parent . left
leftMargin: Theme . horizontalPageMargin
verticalCenter: parent . verticalCenter
}
icon.source: "image://theme/icon-m-cancel"
onClicked: {
chatPage . selectedMessages = [ ] ;
}
}
2020-11-15 01:50:12 +03:00
2020-12-06 17:50:03 +03:00
Row {
spacing: Theme . paddingSmall
anchors {
right: parent . right
rightMargin: Theme . horizontalPageMargin
verticalCenter: parent . verticalCenter
}
2020-11-15 01:50:12 +03:00
2020-12-06 17:50:03 +03:00
IconButton {
icon.source: "../../images/icon-m-copy.svg"
icon.sourceSize: Qt . size ( Theme . iconSizeMedium , Theme . iconSizeMedium )
onClicked: {
Clipboard . text = Functions . getMessagesArrayText ( chatPage . selectedMessages ) ;
appNotification . show ( qsTr ( "%Ln messages have been copied" , "" , selectedMessages . length ) ) ;
chatPage . selectedMessages = [ ] ;
}
}
2020-11-15 01:50:12 +03:00
2020-12-06 17:50:03 +03:00
IconButton {
visible: ! chatPage . isSecretChat && selectedMessages . every ( function ( message ) {
return message . can_be_forwarded
} )
icon.sourceSize: Qt . size ( Theme . iconSizeMedium , Theme . iconSizeMedium )
icon.source: "image://theme/icon-m-forward"
onClicked: {
var ids = Functions . getMessagesArrayIds ( chatPage . selectedMessages )
var neededPermissions = Functions . getMessagesNeededForwardPermissions ( chatPage . selectedMessages )
var chatId = chatInformation . id
pageStack . push ( Qt . resolvedUrl ( "../pages/ChatSelectionPage.qml" ) , {
myUserId: chatPage . myUserId ,
headerDescription: qsTr ( "Forward %Ln messages" , "dialog header" , ids . length ) ,
payload: { fromChatId: chatId , messageIds: ids , neededPermissions: neededPermissions } ,
state: "forwardMessages"
2020-11-15 01:50:12 +03:00
} )
}
2020-12-06 17:50:03 +03:00
}
IconButton {
icon.source: "image://theme/icon-m-delete"
visible: chatInformation . id === chatPage . myUserId || selectedMessages . every ( function ( message ) {
return message . can_be_deleted_for_all_users
} )
icon.sourceSize: Qt . size ( Theme . iconSizeMedium , Theme . iconSizeMedium )
onClicked: {
var ids = Functions . getMessagesArrayIds ( selectedMessages ) ;
var chatId = chatInformation . id
var wrapper = tdLibWrapper ;
Remorse . popupAction ( chatPage , qsTr ( "%Ln Messages deleted" , "" , ids . length ) , function ( ) {
wrapper . deleteMessages ( chatId , ids ) ;
} ) ;
chatPage . selectedMessages = [ ] ;
2020-11-15 01:50:12 +03:00
}
}
}
}
2020-08-21 15:26:56 +03:00
}
}
}