/******************************************************************************* * * Messenger Android Frontend * (C) 2013-2016 Nikolai Kudashov * (C) 2017 Björn Petersen * Contact: r10s@b44t.com, http://b44t.com * * This program 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. * * This program 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 * this program. If not, see http://www.gnu.org/licenses/ . * ******************************************************************************/ package com.b44t.ui.Cells; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Layout; import android.text.Spannable; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.ViewStructure; import com.b44t.messenger.AndroidUtilities; import com.b44t.messenger.ContactsController; import com.b44t.messenger.ImageReceiver; import com.b44t.messenger.LocaleController; import com.b44t.messenger.MediaController; import com.b44t.messenger.FileLoader; import com.b44t.messenger.FileLog; import com.b44t.messenger.MessageObject; import com.b44t.messenger.MessagesController; import com.b44t.messenger.MrChat; import com.b44t.messenger.MrContact; import com.b44t.messenger.MrMailbox; import com.b44t.messenger.R; import com.b44t.messenger.UserObject; import com.b44t.messenger.TLRPC; import com.b44t.ui.Components.AvatarDrawable; import com.b44t.ui.Components.LinkPath; import com.b44t.ui.Components.SeekBar; import com.b44t.ui.Components.SeekBarWaveform; import com.b44t.ui.Components.StaticLayoutEx; import com.b44t.ui.ActionBar.Theme; import com.b44t.ui.PhotoViewer; import java.io.File; import java.util.ArrayList; import java.util.Locale; import static com.b44t.messenger.AndroidUtilities.*; public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate, ImageReceiver.ImageReceiverDelegate { public interface ChatMessageCellDelegate { void didPressedUserAvatar(ChatMessageCell cell, TLRPC.User user); void didPressedChannelAvatar(ChatMessageCell cell, TLRPC.Chat chat, int postId); void didLongPressed(ChatMessageCell cell); void didPressedUrl(MessageObject messageObject, ClickableSpan url, boolean longPress); void didPressedImage(ChatMessageCell cell); void didPressedNewchat(ChatMessageCell cell); boolean needPlayAudio(MessageObject messageObject); boolean canPerformActions(); } private final static int DOCUMENT_ATTACH_TYPE_NONE = 0; private final static int DOCUMENT_ATTACH_TYPE_DOCUMENT = 1; private final static int DOCUMENT_ATTACH_TYPE_GIF = 2; private final static int DOCUMENT_ATTACH_TYPE_VOICE = 3; private final static int DOCUMENT_ATTACH_TYPE_VIDEO = 4; private final static int DOCUMENT_ATTACH_TYPE_MUSIC = 5; private final static int DOCUMENT_ATTACH_TYPE_STICKER = 6; private int textX; private int textY; private int totalHeight; private int keyboardHeight; private int linkBlockNum; private int linkSelectionBlockNum; private Rect scrollRect = new Rect(); private int lastVisibleBlockNum; private int firstVisibleBlockNum; private int totalVisibleBlocksCount; private boolean needNewVisiblePart; private ImageReceiver photoImage; private boolean disallowLongPress; private boolean drawImageButton; private int documentAttachType; private TLRPC.Document documentAttach; private boolean drawPhotoImage; private int mediaOffsetY; private StaticLayout docTitleLayout; private int docTitleOffsetX; private StaticLayout captionLayout; private int captionX; private int captionY; private int captionHeight; private StaticLayout infoLayout; private int infoWidth; private int buttonX; private int buttonY; private final static int BS0_CLICK_TO_PLAY = 0, BS1_CLICK_TO_PAUSE = 1, BS3_NORMAL = 3; private int buttonState; private int buttonPressed; private boolean imagePressed; private boolean photoNotSet; private RectF deleteProgressRect = new RectF(); private RectF rect = new RectF(); private TLRPC.PhotoSize currentPhotoObject; private TLRPC.PhotoSize currentPhotoObjectThumb; private String currentPhotoFilter; private String currentPhotoFilterThumb; private static TextPaint infoPaint; private static TextPaint docNamePaint; private static Paint docBackPaint; private static Paint urlPaint; private static Paint urlSelectionPaint; private ClickableSpan pressedLink; private int pressedLinkType; private boolean linkPreviewPressed; private ArrayList urlPathCache = new ArrayList<>(); private ArrayList urlPath = new ArrayList<>(); private ArrayList urlPathSelection = new ArrayList<>(); private int iconX, iconY; private boolean useSeekBarWaweform; private SeekBar seekBar; private SeekBarWaveform seekBarWaveform; private int seekBarX; private int seekBarY; private StaticLayout durationLayout; private String lastTimeString; private int timeWidthAudio; private int timeAudioX; private static TextPaint audioTimePaint; private static TextPaint audioTitlePaint; private static TextPaint audioPerformerPaint; private StaticLayout songLayout; private int songX; private StaticLayout performerLayout; private int performerX; //private int TAG; public boolean isChat; private boolean isPressed; private boolean isHighlighted; private boolean mediaBackground; private boolean isCheckPressed = true; private boolean wasLayout; private boolean isAvatarVisible; private boolean drawBackground = true; private int substractBackgroundHeight; private boolean allowAssistant; private Drawable currentBackgroundDrawable; private MessageObject currentMessageObject; private int availableTimeWidth; private static TextPaint timePaint; private static TextPaint namePaint; private static TextPaint forwardNamePaint; private static TextPaint forward2NamePaint; private int backgroundWidth = 100; private int layoutWidth; private int layoutHeight; private ImageReceiver avatarImage; private AvatarDrawable avatarDrawable; private boolean avatarPressed; private boolean forwardNamePressed; private boolean drawNewchatButton; private boolean newchatPressed; private int newchatStartX; private int newchatStartY; private StaticLayout nameLayout; private int nameWidth; private float nameOffsetX; private float nameX; private float nameY; private boolean drawName; private boolean drawNameLayout; private StaticLayout[] forwardedNameLayout = new StaticLayout[2]; private int forwardedNameWidth; private boolean drawForwardedName; private int forwardNameX; private int forwardNameY; private float forwardNameOffsetX[] = new float[2]; private StaticLayout timeLayout; private int timeWidth; private int timeTextWidth; private int timeX; private String currentTimeString; private boolean drawTime = true; private TLRPC.User currentUser; private TLRPC.Chat currentChat; private TLRPC.FileLocation currentPhoto; private String currentNameString; private String currentForwardNameString; private ChatMessageCellDelegate delegate; private int namesOffset; private int lastSendState; private int lastViewsCount; public ChatMessageCell(Context context) { super(context); if (infoPaint == null) { infoPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); infoPaint.setTextSize(dp(12)); docNamePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); docNamePaint.setTextSize(dp(16)); docBackPaint = new Paint(Paint.ANTI_ALIAS_FLAG); urlPaint = new Paint(); urlPaint.setColor(Theme.MSG_LINK_SELECT_BACKGROUND_COLOR); urlSelectionPaint = new Paint(); urlSelectionPaint.setColor(Theme.MSG_TEXT_SELECT_BACKGROUND_COLOR); audioTimePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); audioTimePaint.setTextSize(dp(12)); audioTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); audioTitlePaint.setTextSize(dp(16)); audioPerformerPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); audioPerformerPaint.setTextSize(dp(15)); timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); timePaint.setTextSize(dp(12)); namePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); namePaint.setTypeface(Typeface.DEFAULT_BOLD); namePaint.setTextSize(dp(14)); forwardNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); forwardNamePaint.setTextSize(dp(14)); forward2NamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); forward2NamePaint.setTypeface(Typeface.DEFAULT_BOLD); forward2NamePaint.setTextSize(dp(14)); } avatarImage = new ImageReceiver(this); avatarImage.setRoundRadius(dp(21)); avatarDrawable = new AvatarDrawable(); photoImage = new ImageReceiver(this); photoImage.setDelegate(this); seekBar = new SeekBar(context); seekBar.setDelegate(this); seekBarWaveform = new SeekBarWaveform(context); seekBarWaveform.setDelegate(this); seekBarWaveform.setParentView(this); } private void resetPressedLink(int type) { if (pressedLink == null || pressedLinkType != type && type != -1) { return; } resetUrlPaths(false); pressedLink = null; pressedLinkType = -1; invalidate(); } private void resetUrlPaths(boolean text) { if (text) { if (urlPathSelection.isEmpty()) { return; } urlPathCache.addAll(urlPathSelection); urlPathSelection.clear(); } else { if (urlPath.isEmpty()) { return; } urlPathCache.addAll(urlPath); urlPath.clear(); } } private LinkPath obtainNewUrlPath(boolean text) { LinkPath linkPath; if (!urlPathCache.isEmpty()) { linkPath = urlPathCache.get(0); urlPathCache.remove(0); } else { linkPath = new LinkPath(); } if (text) { urlPathSelection.add(linkPath); } else { urlPath.add(linkPath); } return linkPath; } private boolean checkTextBlockMotionEvent(MotionEvent event) { if (currentMessageObject.type != MessageObject.MO_TYPE0_TEXT || currentMessageObject.textLayoutBlocks == null || currentMessageObject.textLayoutBlocks.isEmpty() || !(currentMessageObject.messageText instanceof Spannable)) { return false; } if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP && pressedLinkType == 1) { int x = (int) event.getX(); int y = (int) event.getY(); if (x >= textX && y >= textY && x <= textX + currentMessageObject.textWidth && y <= textY + currentMessageObject.textHeight) { y -= textY; int blockNum = 0; for (int a = 0; a < currentMessageObject.textLayoutBlocks.size(); a++) { if (currentMessageObject.textLayoutBlocks.get(a).textYOffset > y) { break; } blockNum = a; } try { MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(blockNum); x -= textX - (int) Math.ceil(block.textXOffset); y -= block.textYOffset; final int line = block.textLayout.getLineForVertical(y); final int off = block.textLayout.getOffsetForHorizontal(line, x) + block.charactersOffset; final float left = block.textLayout.getLineLeft(line); if (left <= x && left + block.textLayout.getLineWidth(line) >= x) { Spannable buffer = (Spannable) currentMessageObject.messageText; ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); boolean ignore = false; if ( link.length == 0 ) { ignore = true; } if (!ignore) { if (event.getAction() == MotionEvent.ACTION_DOWN) { pressedLink = link[0]; linkBlockNum = blockNum; pressedLinkType = 1; resetUrlPaths(false); try { LinkPath path = obtainNewUrlPath(false); int start = buffer.getSpanStart(pressedLink) - block.charactersOffset; int end = buffer.getSpanEnd(pressedLink); int length = block.textLayout.getText().length(); path.setCurrentLayout(block.textLayout, start, 0); block.textLayout.getSelectionPath(start, end - block.charactersOffset, path); if (end >= block.charactersOffset + length) { for (int a = blockNum + 1; a < currentMessageObject.textLayoutBlocks.size(); a++) { MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a); length = nextBlock.textLayout.getText().length(); ClickableSpan[] nextLink = buffer.getSpans(nextBlock.charactersOffset, nextBlock.charactersOffset, ClickableSpan.class); if (nextLink == null || nextLink.length == 0 || nextLink[0] != pressedLink) { break; } path = obtainNewUrlPath(false); path.setCurrentLayout(nextBlock.textLayout, 0, nextBlock.height); nextBlock.textLayout.getSelectionPath(0, end - nextBlock.charactersOffset, path); if (end < block.charactersOffset + length - 1) { break; } } } if (start < 0) { for (int a = blockNum - 1; a >= 0; a--) { MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a); length = nextBlock.textLayout.getText().length(); ClickableSpan[] nextLink = buffer.getSpans(nextBlock.charactersOffset + length - 1, nextBlock.charactersOffset + length - 1, ClickableSpan.class); if (nextLink == null || nextLink.length == 0 || nextLink[0] != pressedLink) { break; } path = obtainNewUrlPath(false); start = buffer.getSpanStart(pressedLink) - nextBlock.charactersOffset; path.setCurrentLayout(nextBlock.textLayout, start, -nextBlock.height); nextBlock.textLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink) - nextBlock.charactersOffset, path); if (start >= 0) { break; } } } } catch (Exception e) { FileLog.e("messenger", e); } invalidate(); return true; } else { if (link[0] == pressedLink) { delegate.didPressedUrl(currentMessageObject, pressedLink, false); resetPressedLink(1); return true; } } } } } catch (Exception e) { FileLog.e("messenger", e); } } else { resetPressedLink(1); } } return false; } private boolean checkCaptionMotionEvent(MotionEvent event) { if (!(currentMessageObject.caption instanceof Spannable) || captionLayout == null) { return false; } if (event.getAction() == MotionEvent.ACTION_DOWN || (linkPreviewPressed || pressedLink != null) && event.getAction() == MotionEvent.ACTION_UP) { int x = (int) event.getX(); int y = (int) event.getY(); if (x >= captionX && x <= captionX + backgroundWidth && y >= captionY && y <= captionY + captionHeight) { if (event.getAction() == MotionEvent.ACTION_DOWN) { try { x -= captionX; y -= captionY; final int line = captionLayout.getLineForVertical(y); final int off = captionLayout.getOffsetForHorizontal(line, x); final float left = captionLayout.getLineLeft(line); if (left <= x && left + captionLayout.getLineWidth(line) >= x) { Spannable buffer = (Spannable) currentMessageObject.caption; ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); boolean ignore = false; if (link.length == 0 ) { ignore = true; } if (!ignore) { pressedLink = link[0]; pressedLinkType = 3; resetUrlPaths(false); try { LinkPath path = obtainNewUrlPath(false); int start = buffer.getSpanStart(pressedLink); path.setCurrentLayout(captionLayout, start, 0); captionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), path); } catch (Exception e) { FileLog.e("messenger", e); } invalidate(); return true; } } } catch (Exception e) { FileLog.e("messenger", e); } } else if (pressedLinkType == 3) { delegate.didPressedUrl(currentMessageObject, pressedLink, false); resetPressedLink(3); return true; } } else { resetPressedLink(3); } } return false; } private boolean checkPhotoImageMotionEvent(MotionEvent event) { if (!drawPhotoImage && documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT) { return false; } int x = (int) event.getX(); int y = (int) event.getY(); boolean result = false; if (event.getAction() == MotionEvent.ACTION_DOWN) { if (buttonState != -1 && x >= buttonX && x <= buttonX + dp(48) && y >= buttonY && y <= buttonY + dp(48)) { buttonPressed = 1; invalidate(); result = true; } else { if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { if (x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth - dp(50) && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { imagePressed = true; result = true; } } else if (currentMessageObject.type != MessageObject.MO_TYPE13_STICKER || currentMessageObject.getInputStickerSet() != null) { if (x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { imagePressed = true; result = true; } } } if (imagePressed) { if (currentMessageObject.type == MessageObject.MO_TYPE8_GIF && buttonState == -1 && MediaController.getInstance().canAutoplayGifs() && photoImage.getAnimation() == null) { imagePressed = false; result = false; } } } else { if (event.getAction() == MotionEvent.ACTION_UP) { if (buttonPressed == 1) { buttonPressed = 0; playSoundEffect(SoundEffectConstants.CLICK); didPressedButton(); invalidate(); } else if (imagePressed) { imagePressed = false; if (buttonState == -1 || buttonState == BS3_NORMAL) { playSoundEffect(SoundEffectConstants.CLICK); didClickedImage(); } else if (buttonState == BS0_CLICK_TO_PLAY && documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { playSoundEffect(SoundEffectConstants.CLICK); didPressedButton(); } invalidate(); } } } return result; } private boolean checkAudioMotionEvent(MotionEvent event) { if (documentAttachType != DOCUMENT_ATTACH_TYPE_VOICE && documentAttachType != DOCUMENT_ATTACH_TYPE_MUSIC) { return false; } int x = (int) event.getX(); int y = (int) event.getY(); boolean result; if (useSeekBarWaweform) { result = seekBarWaveform.onTouch(event.getAction(), event.getX() - seekBarX - dp(13), event.getY() - seekBarY); } else { result = seekBar.onTouch(event.getAction(), event.getX() - seekBarX, event.getY() - seekBarY); } if (result) { if (!useSeekBarWaweform && event.getAction() == MotionEvent.ACTION_DOWN) { getParent().requestDisallowInterceptTouchEvent(true); } else if (useSeekBarWaweform && !seekBarWaveform.isStartDraging() && event.getAction() == MotionEvent.ACTION_UP) { didPressedButton(); } disallowLongPress = true; invalidate(); } else { int side = dp(36); boolean area; if (buttonState == BS0_CLICK_TO_PLAY || buttonState == BS1_CLICK_TO_PAUSE) { area = x >= buttonX - dp(12) && x <= buttonX - dp(12) + backgroundWidth && y >= namesOffset + mediaOffsetY && y <= layoutHeight; } else { area = x >= buttonX && x <= buttonX + side && y >= buttonY && y <= buttonY + side; } if (event.getAction() == MotionEvent.ACTION_DOWN) { if (area) { buttonPressed = 1; invalidate(); result = true; } } else if (buttonPressed != 0) { if (event.getAction() == MotionEvent.ACTION_UP) { buttonPressed = 0; playSoundEffect(SoundEffectConstants.CLICK); didPressedButton(); invalidate(); } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { buttonPressed = 0; invalidate(); } else if (event.getAction() == MotionEvent.ACTION_MOVE) { if (!area) { buttonPressed = 0; invalidate(); } } } } return result; } @Override public boolean onTouchEvent(MotionEvent event) { if (currentMessageObject == null || !delegate.canPerformActions()) { return super.onTouchEvent(event); } disallowLongPress = false; boolean result = checkTextBlockMotionEvent(event); /*if (!result) { result = checkLinkPreviewMotionEvent(event); }*/ if (!result) { result = checkCaptionMotionEvent(event); } if (!result) { result = checkAudioMotionEvent(event); } if (!result) { result = checkPhotoImageMotionEvent(event); if( result ) { result = true; } } if (event.getAction() == MotionEvent.ACTION_CANCEL) { buttonPressed = 0; linkPreviewPressed = false; imagePressed = false; result = false; resetPressedLink(-1); } if (!disallowLongPress && result && event.getAction() == MotionEvent.ACTION_DOWN) { startCheckLongPress(); } if (event.getAction() != MotionEvent.ACTION_DOWN && event.getAction() != MotionEvent.ACTION_MOVE) { cancelCheckLongPress(); } if (!result) { float x = event.getX(); float y = event.getY(); if (event.getAction() == MotionEvent.ACTION_DOWN) { if (delegate == null || delegate.canPerformActions()) { if (isAvatarVisible && avatarImage.isInsideImage(x, y)) { avatarPressed = true; result = true; } else if (drawForwardedName && forwardedNameLayout[0] != null && x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + dp(32)) { forwardNamePressed = true; result = true; } else if (drawNewchatButton && x >= newchatStartX && x <= newchatStartX + dp(40) && y >= newchatStartY && y <= newchatStartY + dp(32)) { newchatPressed = true; result = true; invalidate(); } if (result) { startCheckLongPress(); } } } else { if (event.getAction() != MotionEvent.ACTION_MOVE) { cancelCheckLongPress(); } if (avatarPressed) { if (event.getAction() == MotionEvent.ACTION_UP) { avatarPressed = false; playSoundEffect(SoundEffectConstants.CLICK); if (delegate != null) { if (currentUser != null) { delegate.didPressedUserAvatar(this, currentUser); } else if (currentChat != null) { delegate.didPressedChannelAvatar(this, currentChat, 0); } } } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { avatarPressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { if (isAvatarVisible && !avatarImage.isInsideImage(x, y)) { avatarPressed = false; } } } else if (forwardNamePressed) { if (event.getAction() == MotionEvent.ACTION_UP) { forwardNamePressed = false; } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { forwardNamePressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + dp(32))) { forwardNamePressed = false; } } } else if (newchatPressed) { if (event.getAction() == MotionEvent.ACTION_UP) { newchatPressed = false; playSoundEffect(SoundEffectConstants.CLICK); if (delegate != null) { delegate.didPressedNewchat(this); } } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { newchatPressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { if (!(x >= newchatStartX && x <= newchatStartX + dp(40) && y >= newchatStartY && y <= newchatStartY + dp(32))) { newchatPressed = false; } } invalidate(); } } } return result; } public void updateAudioProgress() { if (currentMessageObject == null || documentAttach == null) { return; } if (useSeekBarWaweform) { if (!seekBarWaveform.isDragging()) { seekBarWaveform.setProgress(currentMessageObject.audioProgress); } } else { if (!seekBar.isDragging()) { seekBar.setProgress(currentMessageObject.audioProgress); } } int duration = 0; if (documentAttachType == DOCUMENT_ATTACH_TYPE_VOICE) { if (!MediaController.getInstance().isMessageOnAir(currentMessageObject)) { for (int a = 0; a < documentAttach.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = documentAttach.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeAudio) { duration = attribute.duration; break; } } } else { duration = currentMessageObject.audioProgressSec; } String timeString = String.format("%02d:%02d", duration / 60, duration % 60); if (lastTimeString == null || !lastTimeString.equals(timeString)) { lastTimeString = timeString; timeWidthAudio = (int) Math.ceil(audioTimePaint.measureText(timeString)); durationLayout = new StaticLayout(timeString, audioTimePaint, timeWidthAudio, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } } else { int currentProgress = 0; for (int a = 0; a < documentAttach.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = documentAttach.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeAudio) { duration = attribute.duration; break; } } if (MediaController.getInstance().isMessageOnAir(currentMessageObject)) { currentProgress = currentMessageObject.audioProgressSec; } String timeString = String.format("%d:%02d / %d:%02d", currentProgress / 60, currentProgress % 60, duration / 60, duration % 60); if (lastTimeString == null || !lastTimeString.equals(timeString)) { lastTimeString = timeString; int timeWidth = (int) Math.ceil(audioTimePaint.measureText(timeString)); durationLayout = new StaticLayout(timeString, audioTimePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } } invalidate(); } public void setVisiblePart(int position, int height) { if (currentMessageObject == null || currentMessageObject.textLayoutBlocks == null) { return; } position -= textY; int newFirst = -1, newLast = -1, newCount = 0; int startBlock = 0; for (int a = 0; a < currentMessageObject.textLayoutBlocks.size(); a++) { if (currentMessageObject.textLayoutBlocks.get(a).textYOffset > position) { break; } startBlock = a; } for (int a = startBlock; a < currentMessageObject.textLayoutBlocks.size(); a++) { MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(a); float y = block.textYOffset; if (intersect(y, y + block.height, position, position + height)) { if (newFirst == -1) { newFirst = a; } newLast = a; newCount++; } else if (y > position) { break; } } if (lastVisibleBlockNum != newLast || firstVisibleBlockNum != newFirst || totalVisibleBlocksCount != newCount) { lastVisibleBlockNum = newLast; firstVisibleBlockNum = newFirst; totalVisibleBlocksCount = newCount; invalidate(); } } private boolean intersect(float left1, float right1, float left2, float right2) { if (left1 <= left2) { return right1 >= left2; } return left1 <= right2; } private void didClickedImage() { if (currentMessageObject.type == MessageObject.MO_TYPE1_PHOTO || currentMessageObject.type == MessageObject.MO_TYPE13_STICKER) { if (buttonState == -1) { delegate.didPressedImage(this); } else if (buttonState == BS0_CLICK_TO_PLAY) { didPressedButton(); } } else if (currentMessageObject.type == MessageObject.MO_TYPE8_GIF) { if (buttonState == -1) { if (MediaController.getInstance().canAutoplayGifs()) { delegate.didPressedImage(this); } else { buttonState = BS0_CLICK_TO_PLAY; currentMessageObject.audioProgress = 1; photoImage.setAllowStartAnimation(false); photoImage.stopAnimation(); invalidate(); } } else if ( buttonState == BS0_CLICK_TO_PLAY) { didPressedButton(); } } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { if (buttonState == BS0_CLICK_TO_PLAY || buttonState == BS3_NORMAL) { didPressedButton(); } } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { if (buttonState == -1) { delegate.didPressedImage(this); } } } private boolean isPhotoDataChanged(MessageObject object) { if (object.type == MessageObject.MO_TYPE0_TEXT || object.type == MessageObject.MO_TYPE14_MUSIC) { return false; } if (currentPhotoObject == null || currentPhotoObject.location instanceof TLRPC.TL_fileLocationUnavailable) { return true; } else if (currentMessageObject != null && photoNotSet) { File cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); if (cacheFile.exists()) { //TODO return true; } } return false; } private boolean isUserDataChanged() { if (currentMessageObject == null || currentUser == null && currentChat == null) { return false; } if (lastSendState != currentMessageObject.messageOwner.send_state) { return true; } if (lastViewsCount != currentMessageObject.messageOwner.views) { return true; } TLRPC.User newUser = null; TLRPC.Chat newChat = null; if (currentMessageObject.isFromUser()) { newUser = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); } else if (currentMessageObject.messageOwner.post) { newChat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); } TLRPC.FileLocation newPhoto = null; if (isAvatarVisible) { if (newUser != null && newUser.photo != null){ newPhoto = newUser.photo.photo_small; } } if (currentPhoto == null && newPhoto != null || currentPhoto != null && newPhoto == null || currentPhoto != null && newPhoto != null && (currentPhoto.local_id != newPhoto.local_id || currentPhoto.volume_id != newPhoto.volume_id)) { return true; } String newNameString = null; if (drawName && isChat && !currentMessageObject.isOutOwner()) { if (newUser != null) { newNameString = UserObject.getUserName(newUser); } else if (newChat != null) { newNameString = newChat.title; } } if (currentNameString == null && newNameString != null || currentNameString != null && newNameString == null || currentNameString != null && newNameString != null && !currentNameString.equals(newNameString)) { return true; } if (drawForwardedName) { newNameString = currentMessageObject.getForwardedName(); return currentForwardNameString == null && newNameString != null || currentForwardNameString != null && newNameString == null || currentForwardNameString != null && newNameString != null && !currentForwardNameString.equals(newNameString); } return false; } public ImageReceiver getPhotoImage() { return photoImage; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); avatarImage.onDetachedFromWindow(); photoImage.onDetachedFromWindow(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); avatarImage.onAttachedToWindow(); if (drawPhotoImage) { if (photoImage.onAttachedToWindow()) { updateButtonState(); } } else { updateButtonState(); } } @Override protected void onLongPress() { if (pressedLink instanceof URLSpan) { delegate.didPressedUrl(currentMessageObject, pressedLink, true); return; } resetPressedLink(-1); if (buttonPressed != 0 ) { buttonPressed = 0; invalidate(); } if (delegate != null) { delegate.didLongPressed(this); } } public void setCheckPressed(boolean value, boolean pressed) { isCheckPressed = value; isPressed = pressed; if (useSeekBarWaweform) { seekBarWaveform.setSelected(isDrawSelectedBackground()); } else { seekBar.setSelected(isDrawSelectedBackground()); } invalidate(); } public void setHighlighted(boolean value) { if (isHighlighted == value) { return; } isHighlighted = value; if (useSeekBarWaweform) { seekBarWaveform.setSelected(isDrawSelectedBackground()); } else { seekBar.setSelected(isDrawSelectedBackground()); } invalidate(); } @Override public void setPressed(boolean pressed) { super.setPressed(pressed); if (useSeekBarWaweform) { seekBarWaveform.setSelected(isDrawSelectedBackground()); } else { seekBar.setSelected(isDrawSelectedBackground()); } invalidate(); } @Override public void onSeekBarDrag(float progress) { if (currentMessageObject == null) { return; } currentMessageObject.audioProgress = progress; MediaController.getInstance().seekToProgress(currentMessageObject, progress); } private void updateWaveform() { if (currentMessageObject == null || documentAttachType != DOCUMENT_ATTACH_TYPE_VOICE) { return; } for (int a = 0; a < documentAttach.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = documentAttach.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeAudio) { /* this does not work and result in endless reloading of the messages in the chatlist (you can check this using the `Log.i()`-command in ChatActivity if (attribute.waveform == null || attribute.waveform.length == 0) { MediaController.getInstance().generateWaveform(currentMessageObject); } useSeekBarWaweform = attribute.waveform != null; seekBarWaveform.setWaveform(attribute.waveform); */ break; } } } private void createDocumentLayout(int maxWidth, MessageObject messageObject) { documentAttach = messageObject.messageOwner.media.document; if (documentAttach == null) { return; } if (MessageObject.isVoiceDocument(documentAttach)) { documentAttachType = DOCUMENT_ATTACH_TYPE_VOICE; int duration = 0; for (int a = 0; a < documentAttach.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = documentAttach.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeAudio) { duration = attribute.duration; break; } } availableTimeWidth = maxWidth - dp(76 + 18) - (int) Math.ceil(audioTimePaint.measureText("00:00")); measureTime(messageObject); int minSize = dp(40 + 14 + 20 + 90 + 10) + timeWidth; backgroundWidth = Math.min(maxWidth, minSize + duration * dp(10)); if (messageObject.isOutOwner()) { seekBarWaveform.setColors(Theme.MSG_OUT_VOICE_SEEKBAR_COLOR, Theme.MSG_OUT_VOICE_SEEKBAR_FILL_COLOR, Theme.MSG_OUT_VOICE_SEEKBAR_SELECTED_COLOR); } else { seekBarWaveform.setColors(Theme.MSG_IN_VOICE_SEEKBAR_COLOR, Theme.MSG_IN_VOICE_SEEKBAR_FILL_COLOR, Theme.MSG_IN_VOICE_SEEKBAR_SELECTED_COLOR); } seekBar.setColors(Theme.MSG_AUDIO_SEEKBAR_LITE_COLOR, Theme.MSG_AUDIO_SEEKBAR_DARK_COLOR, Theme.MSG_AUDIO_SEEKBAR_DARK_COLOR); seekBarWaveform.setMessageObject(messageObject); } else if (MessageObject.isMusicDocument(documentAttach)) { documentAttachType = DOCUMENT_ATTACH_TYPE_MUSIC; seekBar.setColors(Theme.MSG_AUDIO_SEEKBAR_LITE_COLOR, Theme.MSG_AUDIO_SEEKBAR_DARK_COLOR, Theme.MSG_AUDIO_SEEKBAR_DARK_COLOR); maxWidth = maxWidth - dp(86); CharSequence stringFinal = TextUtils.ellipsize(messageObject.getMusicTitle().replace('\n', ' '), audioTitlePaint, maxWidth, TextUtils.TruncateAt.MIDDLE); songLayout = new StaticLayout(stringFinal, audioTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (songLayout.getLineCount() > 0) { songX = -(int) Math.ceil(songLayout.getLineLeft(0)); } stringFinal = TextUtils.ellipsize(messageObject.getMusicAuthor().replace('\n', ' '), audioPerformerPaint, maxWidth, TextUtils.TruncateAt.END); performerLayout = new StaticLayout(stringFinal, audioPerformerPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (performerLayout.getLineCount() > 0) { performerX = -(int) Math.ceil(performerLayout.getLineLeft(0)); } int duration = 0; for (int a = 0; a < documentAttach.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = documentAttach.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeAudio) { duration = attribute.duration; break; } } int durationWidth = (int) Math.ceil(audioTimePaint.measureText(String.format("%d:%02d / %d:%02d", duration / 60, duration % 60, duration / 60, duration % 60))); availableTimeWidth = backgroundWidth - dp(76 + 18) - durationWidth; } else if (MessageObject.isVideoDocument(documentAttach)) { documentAttachType = DOCUMENT_ATTACH_TYPE_VIDEO; int duration = 0; for (int a = 0; a < documentAttach.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = documentAttach.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeVideo) { duration = attribute.duration; break; } } int minutes = duration / 60; int seconds = duration - minutes * 60; String str = String.format("%d:%02d, %s", minutes, seconds, formatFileSize(documentAttach.size)); infoWidth = (int) Math.ceil(infoPaint.measureText(str)); infoLayout = new StaticLayout(str, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } else { drawPhotoImage = documentAttach.mime_type != null && documentAttach.mime_type.toLowerCase().startsWith("image/") || documentAttach.thumb instanceof TLRPC.TL_photoSize && !(documentAttach.thumb.location instanceof TLRPC.TL_fileLocationUnavailable); if (!drawPhotoImage) { maxWidth += dp(30); } documentAttachType = DOCUMENT_ATTACH_TYPE_DOCUMENT; String name = FileLoader.getDocumentFileName(documentAttach); if (name == null || name.length() == 0) { name = LocaleController.getString("AttachDocument", R.string.AttachDocument); } docTitleLayout = StaticLayoutEx.createStaticLayout(name, docNamePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.MIDDLE, maxWidth, drawPhotoImage ? 2 : 1); docTitleOffsetX = Integer.MIN_VALUE; int width; if (docTitleLayout != null && docTitleLayout.getLineCount() > 0) { int maxLineWidth = 0; for (int a = 0; a < docTitleLayout.getLineCount(); a++) { maxLineWidth = Math.max(maxLineWidth, (int) Math.ceil(docTitleLayout.getLineWidth(a))); docTitleOffsetX = Math.max(docTitleOffsetX, (int) Math.ceil(-docTitleLayout.getLineLeft(a))); } width = Math.min(maxWidth, maxLineWidth); } else { width = maxWidth; docTitleOffsetX = 0; } String str = formatFileSize(documentAttach.size) + " " + FileLoader.getDocumentExtension(documentAttach); infoWidth = Math.min(maxWidth - AndroidUtilities.dp(30), (int) Math.ceil(infoPaint.measureText(str))); CharSequence str2 = TextUtils.ellipsize(str, infoPaint, infoWidth, TextUtils.TruncateAt.END); try { if (infoWidth < 0) { infoWidth = dp(10); } infoLayout = new StaticLayout(str2, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } catch (Exception e) { FileLog.e("messenger", e); } if (drawPhotoImage) { currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, getPhotoSize()); photoImage.setNeedsQualityThumb(true); photoImage.setShouldGenerateQualityThumb(true); photoImage.setParentMessageObject(messageObject); if (currentPhotoObject != null) { currentPhotoFilter = "86_86_b"; photoImage.setImage(null, null, null, null, currentPhotoObject.location, currentPhotoFilter, 0, null, true); } else { photoImage.setImageBitmap((BitmapDrawable) null); } } } } private void calcBackgroundWidth(int maxWidth, int timeMore, int maxChildWidth) { if (maxWidth - currentMessageObject.lastLineWidth < timeMore) { totalHeight += dp(14); backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth) + dp(31); backgroundWidth = Math.max(backgroundWidth, timeWidth + dp(31)); } else { int diff = maxChildWidth - currentMessageObject.lastLineWidth; if (diff >= 0 && diff <= timeMore) { backgroundWidth = maxChildWidth + timeMore - diff + dp(31); } else { backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth + timeMore) + dp(31); } } } public void setHighlightedText(String text) { if (currentMessageObject.messageOwner.message == null || currentMessageObject == null || currentMessageObject.type != MessageObject.MO_TYPE0_TEXT || TextUtils.isEmpty(currentMessageObject.messageText) || text == null) { if (!urlPathSelection.isEmpty()) { linkSelectionBlockNum = -1; resetUrlPaths(true); invalidate(); } return; } int start = TextUtils.indexOf(currentMessageObject.messageOwner.message.toLowerCase(), text.toLowerCase()); if (start == -1) { if (!urlPathSelection.isEmpty()) { linkSelectionBlockNum = -1; resetUrlPaths(true); invalidate(); } return; } int end = start + text.length(); for (int c = 0; c < currentMessageObject.textLayoutBlocks.size(); c++) { MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(c); if (start >= block.charactersOffset && start < block.charactersOffset + block.textLayout.getText().length()) { linkSelectionBlockNum = c; resetUrlPaths(true); try { LinkPath path = obtainNewUrlPath(true); int length = block.textLayout.getText().length(); path.setCurrentLayout(block.textLayout, start, 0); block.textLayout.getSelectionPath(start, end - block.charactersOffset, path); if (end >= block.charactersOffset + length) { for (int a = c + 1; a < currentMessageObject.textLayoutBlocks.size(); a++) { MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a); length = nextBlock.textLayout.getText().length(); path = obtainNewUrlPath(true); path.setCurrentLayout(nextBlock.textLayout, 0, nextBlock.height); nextBlock.textLayout.getSelectionPath(0, end - nextBlock.charactersOffset, path); if (end < block.charactersOffset + length - 1) { break; } } } } catch (Exception e) { FileLog.e("messenger", e); } invalidate(); break; } } } public void setMessageObject(MessageObject messageObject) { boolean messageChanged = currentMessageObject != messageObject || messageObject.forceUpdate; boolean dataChanged = currentMessageObject == messageObject && (isUserDataChanged() || photoNotSet); if (messageChanged || dataChanged || isPhotoDataChanged(messageObject)) { currentMessageObject = messageObject; lastSendState = messageObject.messageOwner.send_state; lastViewsCount = messageObject.messageOwner.views; isPressed = false; isCheckPressed = true; isAvatarVisible = false; wasLayout = false; drawNewchatButton = checkNeedDrawNewchatButton(messageObject); currentUser = null; currentChat = null; drawNameLayout = false; resetPressedLink(-1); messageObject.forceUpdate = false; drawPhotoImage = false; linkPreviewPressed = false; buttonPressed = 0; mediaOffsetY = 0; documentAttachType = DOCUMENT_ATTACH_TYPE_NONE; documentAttach = null; captionLayout = null; docTitleLayout = null; drawImageButton = false; currentPhotoObject = null; currentPhotoObjectThumb = null; currentPhotoFilter = null; infoLayout = null; buttonState = -1; photoNotSet = false; drawBackground = true; drawName = false; useSeekBarWaweform = false; drawForwardedName = false; mediaBackground = false; availableTimeWidth = 0; photoImage.setNeedsQualityThumb(false); photoImage.setShouldGenerateQualityThumb(false); photoImage.setParentMessageObject(null); photoImage.setRoundRadius(dp(3)); if (messageChanged) { firstVisibleBlockNum = 0; lastVisibleBlockNum = 0; needNewVisiblePart = true; } if (messageObject.type == MessageObject.MO_TYPE0_TEXT) { drawForwardedName = true; int maxWidth; if (isTablet()) { if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { maxWidth = getMinTabletSide() - dp(122); drawName = true; } else { drawName = false; maxWidth = getMinTabletSide() - dp(80); } } else { if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { maxWidth = Math.min(displaySize.x, displaySize.y) - dp(122); drawName = true; } else { maxWidth = Math.min(displaySize.x, displaySize.y) - dp(80); drawName = false; } } measureTime(messageObject); int timeMore = timeWidth + dp(6); if (messageObject.isOutOwner()) { timeMore += dp(20.5f); } backgroundWidth = maxWidth; if (maxWidth - messageObject.lastLineWidth < timeMore) { backgroundWidth = Math.max(backgroundWidth, messageObject.lastLineWidth) + dp(31); backgroundWidth = Math.max(backgroundWidth, timeWidth + dp(31)); } else { int diff = backgroundWidth - messageObject.lastLineWidth; if (diff >= 0 && diff <= timeMore) { backgroundWidth = backgroundWidth + timeMore - diff + dp(31); } else { backgroundWidth = Math.max(backgroundWidth, messageObject.lastLineWidth + timeMore) + dp(31); } } availableTimeWidth = backgroundWidth - dp(31); setMessageObjectInternal(messageObject); backgroundWidth = messageObject.textWidth; totalHeight = messageObject.textHeight + dp(19.5f) + namesOffset; int maxChildWidth = Math.max(backgroundWidth, nameWidth); maxChildWidth = Math.max(maxChildWidth, forwardedNameWidth); photoImage.setImageBitmap((Drawable) null); calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } else if (messageObject.type == MessageObject.MO_TYPE2_VOICE) { drawForwardedName = true; if (isTablet()) { backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } else { backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } createDocumentLayout(backgroundWidth, messageObject); setMessageObjectInternal(messageObject); totalHeight = dp(70) + namesOffset; } else if (messageObject.type == MessageObject.MO_TYPE14_MUSIC) { if (isTablet()) { backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } else { backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } createDocumentLayout(backgroundWidth, messageObject); setMessageObjectInternal(messageObject); totalHeight = dp(82) + namesOffset; } else { drawForwardedName = messageObject.messageOwner.fwd_from != null && messageObject.type != MessageObject.MO_TYPE13_STICKER; mediaBackground = messageObject.type != MessageObject.MO_TYPE9_FILE; drawImageButton = false; // we do not want the image button for images drawPhotoImage = true; int photoWidth = 0; int photoHeight = 0; int additionHeight = 0; if (messageObject.audioProgress != 2 && !MediaController.getInstance().canAutoplayGifs() && messageObject.type == MessageObject.MO_TYPE8_GIF) { messageObject.audioProgress = 1; } photoImage.setAllowStartAnimation(messageObject.audioProgress == 0); photoImage.setForcePreview(false); if (messageObject.type == MessageObject.MO_TYPE9_FILE) { if (isTablet()) { backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } else { backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); } if (checkNeedDrawNewchatButton(messageObject)) { backgroundWidth -= dp(20); } int maxWidth = backgroundWidth - dp(86 + 52); createDocumentLayout(maxWidth, messageObject); if (!TextUtils.isEmpty(messageObject.caption)) { maxWidth += AndroidUtilities.dp(86); } if (drawPhotoImage) { photoWidth = dp(86); photoHeight = dp(86); } else { photoWidth = dp(56); photoHeight = dp(56); maxWidth += AndroidUtilities.dp(TextUtils.isEmpty(messageObject.caption) ? 51 : 21); } availableTimeWidth = maxWidth; if (!drawPhotoImage) { if (TextUtils.isEmpty(messageObject.caption) && infoLayout.getLineCount() > 0) { measureTime(messageObject); int timeLeft = backgroundWidth - AndroidUtilities.dp(40 + 18 + 56 + 8) - (int) Math.ceil(infoLayout.getLineWidth(0)); if (timeLeft < timeWidth) { photoHeight += AndroidUtilities.dp(8); } } } } else if (messageObject.type == MessageObject.MO_TYPE13_STICKER) { //webp drawBackground = false; for (int a = 0; a < messageObject.messageOwner.media.document.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = messageObject.messageOwner.media.document.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeImageSize) { photoWidth = attribute.w; photoHeight = attribute.h; break; } } float maxHeight; float maxWidth; if (isTablet()) { maxHeight = maxWidth = getMinTabletSide() * 0.4f; } else { maxHeight = maxWidth = Math.min(displaySize.x, displaySize.y) * 0.5f; } if (photoWidth == 0) { photoHeight = (int) maxHeight; photoWidth = photoHeight + dp(100); } photoHeight *= maxWidth / photoWidth; photoWidth = (int) maxWidth; if (photoHeight > maxHeight) { photoWidth *= maxHeight / photoHeight; photoHeight = (int) maxHeight; } documentAttachType = DOCUMENT_ATTACH_TYPE_STICKER; availableTimeWidth = photoWidth - dp(14); backgroundWidth = photoWidth + dp(12); currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); if (messageObject.attachPathExists) { photoImage.setImage(null, messageObject.messageOwner.attachPath, String.format(Locale.US, "%d_%d", photoWidth, photoHeight), null, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, "b1", messageObject.messageOwner.media.document.size, "webp", true); } else if (messageObject.messageOwner.media.document.id != 0) { photoImage.setImage(messageObject.messageOwner.media.document, null, String.format(Locale.US, "%d_%d", photoWidth, photoHeight), null, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, "b1", messageObject.messageOwner.media.document.size, "webp", true); } } else { int maxPhotoWidth; if (isTablet()) { maxPhotoWidth = photoWidth = (int) (getMinTabletSide() * 0.7f); } else { maxPhotoWidth = photoWidth = (int) (Math.min(displaySize.x, displaySize.y) * 0.7f); } photoHeight = photoWidth + dp(100); if (checkNeedDrawNewchatButton(messageObject)) { maxPhotoWidth -= dp(20); photoWidth -= dp(20); } if (photoWidth > getPhotoSize()) { photoWidth = getPhotoSize(); } if (photoHeight > getPhotoSize()) { photoHeight = getPhotoSize(); } if (messageObject.type == MessageObject.MO_TYPE1_PHOTO) { currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); } else if (messageObject.type == MessageObject.MO_TYPE3_VIDEO) { createDocumentLayout(0, messageObject); photoImage.setNeedsQualityThumb(true); photoImage.setShouldGenerateQualityThumb(true); photoImage.setParentMessageObject(messageObject); } else if (messageObject.type == MessageObject.MO_TYPE8_GIF) { String str = formatFileSize(messageObject.messageOwner.media.document.size); infoWidth = (int) Math.ceil(infoPaint.measureText(str)); infoLayout = new StaticLayout(str, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); photoImage.setNeedsQualityThumb(true); photoImage.setShouldGenerateQualityThumb(true); photoImage.setParentMessageObject(messageObject); } if (messageObject.caption != null) { mediaBackground = false; } currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, getPhotoSize()); int w = 0; int h = 0; if (currentPhotoObject != null && currentPhotoObject == currentPhotoObjectThumb) { currentPhotoObjectThumb = null; } if (currentPhotoObject != null) { float scale = (float) currentPhotoObject.w / (float) photoWidth; w = (int) (currentPhotoObject.w / scale); h = (int) (currentPhotoObject.h / scale); if (w == 0) { w = dp(150); } if (h == 0) { h = dp(150); } if (h > photoHeight) { float scale2 = h; h = photoHeight; scale2 /= h; w = (int) (w / scale2); } else if (h < dp(120)) { h = dp(120); float hScale = (float) currentPhotoObject.h / h; if (currentPhotoObject.w / hScale < photoWidth) { w = (int) (currentPhotoObject.w / hScale); } } } if ((w == 0 || h == 0) && messageObject.type == MessageObject.MO_TYPE8_GIF) { for (int a = 0; a < messageObject.messageOwner.media.document.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = messageObject.messageOwner.media.document.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeImageSize || attribute instanceof TLRPC.TL_documentAttributeVideo) { float scale = (float) attribute.w / (float) photoWidth; w = (int) (attribute.w / scale); h = (int) (attribute.h / scale); if (h > photoHeight) { float scale2 = h; h = photoHeight; scale2 /= h; w = (int) (w / scale2); } else if (h < dp(120)) { h = dp(120); float hScale = (float) attribute.h / h; if (attribute.w / hScale < photoWidth) { w = (int) (attribute.w / hScale); } } break; } } } if (w == 0 || h == 0) { w = h = dp(150); } if (messageObject.type == MessageObject.MO_TYPE3_VIDEO) { if (w < infoWidth + dp(16 + 24)) { w = infoWidth + dp(16 + 24); } } availableTimeWidth = maxPhotoWidth - dp(14); measureTime(messageObject); int timeWidthTotal = timeWidth + dp(14 + (messageObject.isOutOwner() ? 20 : 0)); if (w < timeWidthTotal) { w = timeWidthTotal; } photoWidth = w; photoHeight = h; backgroundWidth = w + dp(12); if (!mediaBackground) { backgroundWidth += dp(9); } if (messageObject.caption != null) { try { captionLayout = new StaticLayout(messageObject.caption, MessageObject.getTextPaint(), photoWidth - dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (captionLayout.getLineCount() > 0) { captionHeight = captionLayout.getHeight(); additionHeight += captionHeight + dp(9); float lastLineWidth = captionLayout.getLineWidth(captionLayout.getLineCount() - 1) + captionLayout.getLineLeft(captionLayout.getLineCount() - 1); if (photoWidth - dp(8) - lastLineWidth < timeWidthTotal) { additionHeight += dp(14); } } } catch (Exception e) { FileLog.e("messenger", e); } } currentPhotoFilter = String.format(Locale.US, "%d_%d", (int) (w / density), (int) (h / density)); if (messageObject.photoThumbs != null && messageObject.photoThumbs.size() > 1 || messageObject.type == MessageObject.MO_TYPE3_VIDEO || messageObject.type == MessageObject.MO_TYPE8_GIF) { currentPhotoFilter += "_b"; } boolean noSize = false; if (messageObject.type == MessageObject.MO_TYPE3_VIDEO || messageObject.type == MessageObject.MO_TYPE8_GIF) { noSize = true; } if (currentPhotoObject != null && !noSize && currentPhotoObject.size == 0) { currentPhotoObject.size = -1; } if (messageObject.type == MessageObject.MO_TYPE1_PHOTO) { if (currentPhotoObject != null) { boolean photoExist = true; //String fileName = FileLoader.getAttachFileName(currentPhotoObject); if (messageObject.mediaExists) { //MediaController.getInstance().removeLoadingFileObserver(this); } else { photoExist = false; } if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) /*|| FileLoader.getInstance().isLoadingFile(fileName)*/) { photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, noSize ? 0 : currentPhotoObject.size, null, false); } else { photoNotSet = true; if (currentPhotoObjectThumb != null) { photoImage.setImage(null, null, currentPhotoObjectThumb.location, currentPhotoFilter, 0, null, false); } else { photoImage.setImageBitmap((Drawable) null); } } } else { photoImage.setImageBitmap((BitmapDrawable) null); } } else if (messageObject.type == MessageObject.MO_TYPE8_GIF) { String fileName = FileLoader.getAttachFileName(messageObject.messageOwner.media.document); int localFile = 0; if (messageObject.attachPathExists) { localFile = 1; } else if (messageObject.mediaExists) { localFile = 2; } if (!messageObject.isSending() && (localFile != 0 || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF) && MessageObject.isNewGifDocument(messageObject.messageOwner.media.document) || FileLoader.getInstance().isLoadingFile(fileName))) { if (localFile == 1) { photoImage.setImage(null, messageObject.isSendError() ? null : messageObject.messageOwner.attachPath, null, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, 0, null, false); } else { photoImage.setImage(messageObject.messageOwner.media.document, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, messageObject.messageOwner.media.document.size, null, false); } } else { photoNotSet = true; photoImage.setImage(null, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, 0, null, false); } } else { photoImage.setImage(null, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, 0, null, false); } } setMessageObjectInternal(messageObject); if (drawForwardedName) { namesOffset += dp(5); } else if (drawNameLayout && messageObject.messageOwner.reply_to_msg_id == 0) { namesOffset += dp(7); } invalidate(); photoImage.setImageCoords(0, dp(7) + namesOffset, photoWidth, photoHeight); totalHeight = photoHeight + dp(14) + namesOffset + additionHeight; } if (captionLayout == null && messageObject.caption != null && messageObject.type != MessageObject.MO_TYPE13_STICKER) { try { int width = backgroundWidth - AndroidUtilities.dp(31); captionLayout = new StaticLayout(messageObject.caption, MessageObject.getTextPaint(), width - dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (captionLayout.getLineCount() > 0) { int timeWidthTotal = timeWidth + (messageObject.isOutOwner() ? dp(20) : 0); captionHeight = captionLayout.getHeight(); totalHeight += captionHeight + dp(9); float lastLineWidth = captionLayout.getLineWidth(captionLayout.getLineCount() - 1) + captionLayout.getLineLeft(captionLayout.getLineCount() - 1); if (width - dp(8) - lastLineWidth < timeWidthTotal) { totalHeight += dp(14); captionHeight += dp(14); } } } catch (Exception e) { FileLog.e("messenger", e); } } { substractBackgroundHeight = 0; keyboardHeight = 0; } } updateWaveform(); updateButtonState(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), totalHeight + keyboardHeight); } @SuppressLint("DrawAllocation") @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (currentMessageObject == null) { super.onLayout(changed, left, top, right, bottom); return; } if (changed || !wasLayout) { layoutWidth = getMeasuredWidth(); layoutHeight = getMeasuredHeight() - substractBackgroundHeight; if (timeTextWidth < 0) { timeTextWidth = dp(10); } timeLayout = new StaticLayout(currentTimeString, timePaint, timeTextWidth + dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (!mediaBackground) { if (!currentMessageObject.isOutOwner()) { timeX = backgroundWidth - dp(9) - timeWidth + (isChat && currentMessageObject.isFromUser() ? dp(48) : 0); } else { timeX = layoutWidth - timeWidth - dp(38.5f); } } else { if (!currentMessageObject.isOutOwner()) { timeX = backgroundWidth - dp(4) - timeWidth + (isChat && currentMessageObject.isFromUser() ? dp(48) : 0); } else { timeX = layoutWidth - timeWidth - dp(42.0f); } } if (isAvatarVisible) { avatarImage.setImageCoords(dp(6), layoutHeight - dp(44), dp(42), dp(42)); } wasLayout = true; } if (currentMessageObject.type == MessageObject.MO_TYPE0_TEXT) { textY = dp(10) + namesOffset; } if (documentAttachType == DOCUMENT_ATTACH_TYPE_VOICE) { if (currentMessageObject.isOutOwner()) { seekBarX = layoutWidth - backgroundWidth + dp(57); buttonX = layoutWidth - backgroundWidth + dp(14); timeAudioX = layoutWidth - backgroundWidth + dp(67); } else { if (isChat && currentMessageObject.isFromUser()) { seekBarX = dp(114); buttonX = dp(71); timeAudioX = dp(124); } else { seekBarX = dp(66); buttonX = dp(23); timeAudioX = dp(76); } } seekBarWaveform.setSize(backgroundWidth - dp(92), dp(30)); seekBar.setSize(backgroundWidth - dp(72), dp(30)); seekBarY = dp(13) + namesOffset + mediaOffsetY; buttonY = dp(13) + namesOffset + mediaOffsetY; iconX = buttonX; iconY = buttonY; updateAudioProgress(); } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { if (currentMessageObject.isOutOwner()) { seekBarX = layoutWidth - backgroundWidth + dp(56); buttonX = layoutWidth - backgroundWidth + dp(14); timeAudioX = layoutWidth - backgroundWidth + dp(67); } else { if (isChat && currentMessageObject.isFromUser()) { seekBarX = dp(113); buttonX = dp(71); timeAudioX = dp(124); } else { seekBarX = dp(65); buttonX = dp(23); timeAudioX = dp(76); } } seekBar.setSize(backgroundWidth - dp(65), dp(30)); seekBarY = dp(29) + namesOffset + mediaOffsetY; buttonY = dp(13) + namesOffset + mediaOffsetY; iconX = buttonX; iconY = buttonY; updateAudioProgress(); } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT && !drawPhotoImage) { if (currentMessageObject.isOutOwner()) { buttonX = layoutWidth - backgroundWidth + dp(14); } else { if (isChat && currentMessageObject.isFromUser()) { buttonX = dp(71); } else { buttonX = dp(23); } } buttonY = dp(13) + namesOffset + mediaOffsetY; iconX = buttonX; iconY = buttonY; photoImage.setImageCoords(buttonX - dp(10), buttonY - dp(10), photoImage.getImageWidth(), photoImage.getImageHeight()); } else { int x; if (currentMessageObject.isOutOwner()) { if (mediaBackground) { x = layoutWidth - backgroundWidth - dp(3); } else { x = layoutWidth - backgroundWidth + dp(6); } } else { if (isChat && currentMessageObject.isFromUser()) { x = dp(63); } else { x = dp(15); } } photoImage.setImageCoords(x, photoImage.getImageY(), photoImage.getImageWidth(), photoImage.getImageHeight()); buttonX = (int) (x + (photoImage.getImageWidth() - dp(48)) / 2.0f); buttonY = (int) (dp(7) + (photoImage.getImageHeight() - dp(48)) / 2.0f) + namesOffset; iconX = buttonX; iconY = buttonY; deleteProgressRect.set(buttonX + dp(3), buttonY + dp(3), buttonX + dp(45), buttonY + dp(45)); } } private void drawContent(Canvas canvas) { if (needNewVisiblePart && currentMessageObject.type == MessageObject.MO_TYPE0_TEXT) { getLocalVisibleRect(scrollRect); setVisiblePart(scrollRect.top, scrollRect.bottom - scrollRect.top); needNewVisiblePart = false; } photoImage.setPressed(isDrawSelectedBackground()); photoImage.setVisible(!PhotoViewer.getInstance().isShowingImage(currentMessageObject), false); boolean imageDrawn = false; int drawIcon = -1; if (currentMessageObject.type == MessageObject.MO_TYPE0_TEXT && currentMessageObject.textLayoutBlocks != null && !currentMessageObject.textLayoutBlocks.isEmpty()) { if (currentMessageObject.isOutOwner()) { textX = currentBackgroundDrawable.getBounds().left + dp(11); } else { textX = currentBackgroundDrawable.getBounds().left + dp(17); } textY = dp(10) + namesOffset; if (firstVisibleBlockNum >= 0) { for (int a = firstVisibleBlockNum; a <= lastVisibleBlockNum; a++) { if (a >= currentMessageObject.textLayoutBlocks.size()) { break; } MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(a); canvas.save(); canvas.translate(textX - (int) Math.ceil(block.textXOffset), textY + block.textYOffset); if (pressedLink != null && a == linkBlockNum) { for (int b = 0; b < urlPath.size(); b++) { canvas.drawPath(urlPath.get(b), urlPaint); } } if (a == linkSelectionBlockNum && !urlPathSelection.isEmpty()) { for (int b = 0; b < urlPathSelection.size(); b++) { canvas.drawPath(urlPathSelection.get(b), urlSelectionPaint); } } try { block.textLayout.draw(canvas); } catch (Exception e) { FileLog.e("messenger", e); } canvas.restore(); } } drawTime = true; } else if (drawPhotoImage) { imageDrawn = photoImage.draw(canvas); drawTime = photoImage.getVisible(); } if (documentAttachType == DOCUMENT_ATTACH_TYPE_GIF || currentMessageObject.type == MessageObject.MO_TYPE8_GIF) { ; } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { drawIcon = buttonState==BS0_CLICK_TO_PLAY? Theme.INLIST_PLAY : Theme.INLIST_PAUSE; audioTitlePaint.setColor(Theme.MSG_AUDIO_NAME_COLOR); audioPerformerPaint.setColor(Theme.MSG_AUDIO_NAME_COLOR); audioTimePaint.setColor(Theme.MSG_AUDIO_NAME_COLOR); canvas.save(); canvas.translate(timeAudioX + songX, dp(13) + namesOffset + mediaOffsetY); songLayout.draw(canvas); canvas.restore(); canvas.save(); if (MediaController.getInstance().isMessageOnAir(currentMessageObject)) { canvas.translate(seekBarX, seekBarY); seekBar.draw(canvas); } else { canvas.translate(timeAudioX + performerX, dp(35) + namesOffset + mediaOffsetY); performerLayout.draw(canvas); } canvas.restore(); canvas.save(); canvas.translate(timeAudioX, dp(57) + namesOffset + mediaOffsetY); durationLayout.draw(canvas); canvas.restore(); } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_VOICE) { drawIcon = buttonState==BS0_CLICK_TO_PLAY? Theme.INLIST_PLAY : Theme.INLIST_PAUSE; if (currentMessageObject.isOutOwner()) { audioTimePaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_AUDIO_DURATION_SELECTED_TEXT_COLOR : Theme.MSG_OUT_AUDIO_DURATION_TEXT_COLOR); } else { audioTimePaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_AUDIO_DURATION_SELECTED_TEXT_COLOR : Theme.MSG_IN_AUDIO_DURATION_TEXT_COLOR); } canvas.save(); if (useSeekBarWaweform) { canvas.translate(seekBarX + dp(13), seekBarY); seekBarWaveform.draw(canvas); } else { canvas.translate(seekBarX, seekBarY); seekBar.draw(canvas); } canvas.restore(); canvas.save(); canvas.translate(timeAudioX, dp(44) + namesOffset + mediaOffsetY); durationLayout.draw(canvas); canvas.restore(); if (!currentMessageObject.isOutOwner() && currentMessageObject.isUnread()) { // mark unread incoming messages with a little dot (only works if we mark the voice messages as being read only if heard) docBackPaint.setColor(Theme.MSG_IN_VOICE_SEEKBAR_FILL_COLOR); canvas.drawCircle(timeAudioX + timeWidthAudio + dp(6), dp(51) + namesOffset + mediaOffsetY, dp(4), docBackPaint); } } if (currentMessageObject.type == MessageObject.MO_TYPE1_PHOTO || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { if (photoImage.getVisible()) { if (infoLayout != null && (buttonState == BS1_CLICK_TO_PAUSE || buttonState == BS0_CLICK_TO_PLAY || buttonState == BS3_NORMAL)) { infoPaint.setColor(Theme.MSG_MEDIA_INFO_TEXT_COLOR); setDrawableBounds(Theme.timeBackgroundDrawable, photoImage.getImageX() + dp(4), photoImage.getImageY() + dp(4), infoWidth + dp(8), dp(16.5f)); Theme.timeBackgroundDrawable.draw(canvas); canvas.save(); canvas.translate(photoImage.getImageX() + dp(8), photoImage.getImageY() + dp(5.5f)); infoLayout.draw(canvas); canvas.restore(); } } } if (captionLayout != null) { canvas.save(); if (currentMessageObject.type == MessageObject.MO_TYPE1_PHOTO || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || currentMessageObject.type == MessageObject.MO_TYPE8_GIF) { canvas.translate(captionX = photoImage.getImageX() + dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + dp(6)); } else { canvas.translate(captionX = currentBackgroundDrawable.getBounds().left + dp(currentMessageObject.isOutOwner() ? 11 : 17), captionY = totalHeight - captionHeight - dp(10)); } if (pressedLink != null) { for (int b = 0; b < urlPath.size(); b++) { canvas.drawPath(urlPath.get(b), urlPaint); } } try { captionLayout.draw(canvas); } catch (Exception e) { FileLog.e("messenger", e); } canvas.restore(); } if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { drawIcon = Theme.INLIST_FILE; docNamePaint.setColor(Theme.MSG_DOC_NAME_COLOR); infoPaint.setColor(Theme.MSG_DOC_NAME_COLOR); if (currentMessageObject.isOutOwner()) { docBackPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_FILE_BACKGROUND_SELECTED_COLOR : Theme.MSG_OUT_FILE_BACKGROUND_COLOR); } else { docBackPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_FILE_BACKGROUND_SELECTED_COLOR : Theme.MSG_IN_FILE_BACKGROUND_COLOR); } int x; int titleY; int subtitleY; if (drawPhotoImage) { x = photoImage.getImageX() + photoImage.getImageWidth() + dp(10); titleY = photoImage.getImageY() + dp(8); subtitleY = photoImage.getImageY() + docTitleLayout.getLineBottom(docTitleLayout.getLineCount() - 1) + dp(13); if (!imageDrawn) { rect.set(photoImage.getImageX(), photoImage.getImageY(), photoImage.getImageX() + photoImage.getImageWidth(), photoImage.getImageY() + photoImage.getImageHeight()); canvas.drawRoundRect(rect, dp(3), dp(3), docBackPaint); } } else { x = buttonX + dp(53); titleY = buttonY + dp(4); subtitleY = buttonY + dp(27); } try { if (docTitleLayout != null) { canvas.save(); canvas.translate(x + docTitleOffsetX, titleY); docTitleLayout.draw(canvas); canvas.restore(); } } catch (Exception e) { FileLog.e("messenger", e); } try { if (infoLayout != null) { canvas.save(); canvas.translate(x, subtitleY); infoLayout.draw(canvas); canvas.restore(); } } catch (Exception e) { FileLog.e("messenger", e); } } if( drawIcon!=-1 ) { setDrawableBounds(Theme.inlistDrawable[drawIcon], iconX, iconY, dp(44), dp(44)); Theme.inlistDrawable[drawIcon].draw(canvas); } } private int getMaxNameWidth() { if (documentAttachType == DOCUMENT_ATTACH_TYPE_STICKER) { int maxWidth; if (isTablet()) { if (isChat && !currentMessageObject.isOutOwner() && currentMessageObject.isFromUser()) { maxWidth = getMinTabletSide() - dp(42); } else { maxWidth = getMinTabletSide(); } } else { if (isChat && !currentMessageObject.isOutOwner() && currentMessageObject.isFromUser()) { maxWidth = Math.min(displaySize.x, displaySize.y) - dp(42); } else { maxWidth = Math.min(displaySize.x, displaySize.y); } } return maxWidth - backgroundWidth - dp(57); } return backgroundWidth - dp(mediaBackground ? 22 : 31); } public void updateButtonState() { if (documentAttachType == DOCUMENT_ATTACH_TYPE_VOICE || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { boolean isMessageOnAir = MediaController.getInstance().isMessageOnAir(currentMessageObject); boolean paused = MediaController.getInstance().isAudioPaused(); if( isMessageOnAir && !paused ) { buttonState = BS1_CLICK_TO_PAUSE; } else { buttonState = BS0_CLICK_TO_PLAY; } updateAudioProgress(); } else if (currentMessageObject.type == MessageObject.MO_TYPE0_TEXT && documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT && documentAttachType != DOCUMENT_ATTACH_TYPE_VIDEO) { if (currentPhotoObject == null || !drawImageButton) { return; } buttonState = -1; invalidate(); } else { if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { buttonState = BS3_NORMAL; } else { buttonState = -1; } if (photoNotSet) { setMessageObject(currentMessageObject); } invalidate(); } } private void didPressedButton() { if (buttonState == BS0_CLICK_TO_PLAY) { if (documentAttachType == DOCUMENT_ATTACH_TYPE_VOICE || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { if (delegate.needPlayAudio(currentMessageObject)) { buttonState = BS1_CLICK_TO_PAUSE; invalidate(); } } else { if (currentMessageObject.type == MessageObject.MO_TYPE1_PHOTO) { photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, currentPhotoObject.size, null, false); } else if (currentMessageObject.type == MessageObject.MO_TYPE8_GIF) { currentMessageObject.audioProgress = 2; photoImage.setImage(currentMessageObject.messageOwner.media.document, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, currentMessageObject.messageOwner.media.document.size, null, false); } else if (currentMessageObject.type == MessageObject.MO_TYPE9_FILE) { FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.document, false, false); } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { FileLoader.getInstance().loadFile(documentAttach, true, false); } else { photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, 0, null, false); } buttonState = BS1_CLICK_TO_PAUSE; invalidate(); } } else if (buttonState == BS1_CLICK_TO_PAUSE) { if (documentAttachType == DOCUMENT_ATTACH_TYPE_VOICE || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { MediaController.getInstance().stopAudio(); // a click on message-pause should do the opposite of message-play - including clearing the status bar (a "real" pause can be done via the status bar) buttonState = BS0_CLICK_TO_PLAY; invalidate(); } else { buttonState = BS0_CLICK_TO_PLAY; invalidate(); } } else if (buttonState == BS3_NORMAL) { delegate.didPressedImage(this); } } @Override public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb) { if (currentMessageObject != null && set && !thumb && !currentMessageObject.mediaExists && !currentMessageObject.attachPathExists) { currentMessageObject.mediaExists = true; updateButtonState(); } } @Override public void onProvideStructure(ViewStructure structure) { super.onProvideStructure(structure); if (allowAssistant && Build.VERSION.SDK_INT >= 23) { if (currentMessageObject.messageText != null && currentMessageObject.messageText.length() > 0) { structure.setText(currentMessageObject.messageText); } else if (currentMessageObject.caption != null && currentMessageObject.caption.length() > 0) { structure.setText(currentMessageObject.caption); } } } public void setDelegate(ChatMessageCellDelegate chatMessageCellDelegate) { delegate = chatMessageCellDelegate; } public void setAllowAssistant(boolean value) { allowAssistant = value; } private void measureTime(MessageObject messageObject) { boolean hasSign = !messageObject.isOutOwner() && messageObject.messageOwner.from_id > 0 && messageObject.messageOwner.post; TLRPC.User signUser = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); if (hasSign && signUser == null) { hasSign = false; } String timeString = LocaleController.getInstance().formatterDay.format((long) (messageObject.messageOwner.date) * 1000); if (hasSign) { currentTimeString = ", " + timeString; } else { currentTimeString = timeString; } timeTextWidth = timeWidth = (int) Math.ceil(timePaint.measureText(currentTimeString)); if (hasSign) { if (availableTimeWidth == 0) { availableTimeWidth = dp(1000); } CharSequence name = ContactsController.formatName(signUser.first_name, signUser.last_name).replace('\n', ' '); int widthForSign = availableTimeWidth - timeWidth; int width = (int) Math.ceil(timePaint.measureText(name, 0, name.length())); if (width > widthForSign) { name = TextUtils.ellipsize(name, timePaint, widthForSign, TextUtils.TruncateAt.END); width = widthForSign; } currentTimeString = name + currentTimeString; timeTextWidth += width; timeWidth += width; } } private boolean isDrawSelectedBackground() { return isPressed() && isCheckPressed || !isCheckPressed && isPressed || isHighlighted; } private boolean checkNeedDrawNewchatButton(MessageObject messageObject) { if( messageObject.getDialogId()== MrChat.MR_CHAT_ID_DEADDROP) { return true; } return false; } private void setMessageObjectInternal(MessageObject messageObject) { if (currentMessageObject.isFromUser()) { currentUser = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); } else if (currentMessageObject.messageOwner.from_id < 0) { currentChat = MessagesController.getInstance().getChat(-currentMessageObject.messageOwner.from_id); } else if (currentMessageObject.messageOwner.post) { currentChat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); } MrContact mrContact = null; String cname = ""; if(currentUser!=null && isChat) { mrContact = MrMailbox.getContact(currentUser.id); cname = mrContact.getDisplayName(); } if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { isAvatarVisible = true; ContactsController.setupAvatar(this, avatarImage, avatarDrawable, mrContact, null); } measureTime(messageObject); namesOffset = 0; boolean authorName = drawName && isChat && !currentMessageObject.isOutOwner(); if (authorName) { drawNameLayout = true; nameWidth = getMaxNameWidth(); if (nameWidth < 0) { nameWidth = dp(100); } if (authorName) { if (currentUser != null) { currentNameString = cname; } else if (currentChat != null) { currentNameString = currentChat.title; } else { currentNameString = "DELETED"; } } else { currentNameString = ""; } CharSequence nameStringFinal = TextUtils.ellipsize(currentNameString.replace('\n', ' '), namePaint, nameWidth, TextUtils.TruncateAt.END); try { nameLayout = new StaticLayout(nameStringFinal, namePaint, nameWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (nameLayout != null && nameLayout.getLineCount() > 0) { nameWidth = (int) Math.ceil(nameLayout.getLineWidth(0)); if (messageObject.type != MessageObject.MO_TYPE13_STICKER) { namesOffset += dp(19); } nameOffsetX = nameLayout.getLineLeft(0); } else { nameWidth = 0; } } catch (Exception e) { FileLog.e("messenger", e); } if (currentNameString.length() == 0) { currentNameString = null; } } else { currentNameString = null; nameLayout = null; nameWidth = 0; } currentForwardNameString = null; forwardedNameLayout[0] = null; forwardedNameLayout[1] = null; forwardedNameWidth = 0; if( drawForwardedName && messageObject.isForwarded() && messageObject.messageOwner.fwd_from!=null ) { currentForwardNameString = messageObject.messageOwner.fwd_from.m_name; forwardedNameWidth = getMaxNameWidth(); int fromWidth = (int) Math.ceil(forwardNamePaint.measureText(LocaleController.getString("From", R.string.From) + " ")); CharSequence name = TextUtils.ellipsize(currentForwardNameString.replace('\n', ' '), forward2NamePaint, forwardedNameWidth - fromWidth, TextUtils.TruncateAt.END); CharSequence lastLine; lastLine = replaceTags(String.format("%s %s", LocaleController.getString("From", R.string.From), name)); lastLine = TextUtils.ellipsize(lastLine, forwardNamePaint, forwardedNameWidth, TextUtils.TruncateAt.END); try { forwardedNameLayout[1] = new StaticLayout(lastLine, forwardNamePaint, forwardedNameWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); lastLine = TextUtils.ellipsize(replaceTags(LocaleController.getString("ForwardedMessage", R.string.ForwardedMessage)), forwardNamePaint, forwardedNameWidth, TextUtils.TruncateAt.END); forwardedNameLayout[0] = new StaticLayout(lastLine, forwardNamePaint, forwardedNameWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); forwardedNameWidth = Math.max((int) Math.ceil(forwardedNameLayout[0].getLineWidth(0)), (int) Math.ceil(forwardedNameLayout[1].getLineWidth(0))); forwardNameOffsetX[0] = forwardedNameLayout[0].getLineLeft(0); forwardNameOffsetX[1] = forwardedNameLayout[1].getLineLeft(0); namesOffset += dp(36); } catch (Exception e) { FileLog.e("messenger", e); } } requestLayout(); } @Override protected void onDraw(Canvas canvas) { if (currentMessageObject == null) { return; } if (!wasLayout) { requestLayout(); return; } if (isAvatarVisible) { avatarImage.draw(canvas); } if (mediaBackground) { timePaint.setColor(Theme.MSG_MEDIA_TIME_TEXT_COLOR); } else { if (currentMessageObject.isOutOwner()) { timePaint.setColor(Theme.MSG_OUT_FWD_N_TIME_TEXT_COLOR); } else { timePaint.setColor(Theme.MSG_IN_FWD_N_TIME_TEXT_COLOR); } } if (currentMessageObject.isOutOwner()) { if (isDrawSelectedBackground()) { currentBackgroundDrawable = Theme.backgroundDrawableOutSelected; } else { currentBackgroundDrawable = Theme.backgroundDrawableOut; } setDrawableBounds(currentBackgroundDrawable, layoutWidth - backgroundWidth - (!mediaBackground ? 0 : dp(9)), dp(1), backgroundWidth - (mediaBackground ? -dp(6) : dp(3)), layoutHeight - dp(2)); } else { if (isDrawSelectedBackground()) { currentBackgroundDrawable = Theme.backgroundDrawableInSelected; } else { currentBackgroundDrawable = Theme.backgroundDrawableIn; } if (isChat && currentMessageObject.isFromUser()) { setDrawableBounds(currentBackgroundDrawable, dp(48 + (!mediaBackground ? 3 : 3)), dp(1), backgroundWidth - (mediaBackground ? -dp(6) : dp(3)), layoutHeight - dp(2)); } else { setDrawableBounds(currentBackgroundDrawable, (!mediaBackground ? dp(3) : dp(3)), dp(1), backgroundWidth - (mediaBackground ? -dp(6) : dp(3)), layoutHeight - dp(2)); } } if (drawBackground && currentBackgroundDrawable != null) { currentBackgroundDrawable.draw(canvas); } drawContent(canvas); if (drawNewchatButton) { newchatStartX = currentBackgroundDrawable.getBounds().right - dp(34); newchatStartY = dp(4); setDrawableBounds(Theme.newchatIconDrawable, newchatStartX, newchatStartY); Theme.newchatIconDrawable.draw(canvas); } if (drawNameLayout && nameLayout != null) { canvas.save(); if (currentMessageObject.type == MessageObject.MO_TYPE13_STICKER) { namePaint.setColor(Theme.MSG_STICKER_NAME_TEXT_COLOR); if (currentMessageObject.isOutOwner()) { nameX = dp(28); } else { nameX = currentBackgroundDrawable.getBounds().right + dp(22); } nameY = layoutHeight - dp(38); Theme.systemDrawable.setColorFilter(Theme.colorFilter); Theme.systemDrawable.setBounds((int) nameX - dp(12), (int) nameY - dp(5), (int) nameX + dp(12) + nameWidth, (int) nameY + dp(22)); Theme.systemDrawable.draw(canvas); } else { if (mediaBackground || currentMessageObject.isOutOwner()) { nameX = currentBackgroundDrawable.getBounds().left + dp(11) - nameOffsetX; } else { nameX = currentBackgroundDrawable.getBounds().left + dp(17) - nameOffsetX; } namePaint.setColor(AvatarDrawable.getNameColor(currentNameString)); nameY = dp(10); } canvas.translate(nameX, nameY); nameLayout.draw(canvas); canvas.restore(); } if (drawForwardedName && forwardedNameLayout[0] != null && forwardedNameLayout[1] != null) { forwardNameY = dp(10 + (drawNameLayout ? 19 : 0)); if (currentMessageObject.isOutOwner()) { forwardNamePaint.setColor(Theme.MSG_OUT_FWD_N_TIME_TEXT_COLOR); forwardNameX = currentBackgroundDrawable.getBounds().left + dp(11); } else { forwardNamePaint.setColor(Theme.MSG_IN_FWD_N_TIME_TEXT_COLOR); if (mediaBackground) { forwardNameX = currentBackgroundDrawable.getBounds().left + dp(11); } else { forwardNameX = currentBackgroundDrawable.getBounds().left + dp(17); } } for (int a = 0; a < 2; a++) { canvas.save(); canvas.translate(forwardNameX - forwardNameOffsetX[a], forwardNameY + dp(16) * a); forwardedNameLayout[a].draw(canvas); canvas.restore(); } } if (drawTime || !mediaBackground) { if (mediaBackground) { Drawable drawable; if (currentMessageObject.type == MessageObject.MO_TYPE13_STICKER) { drawable = Theme.timeStickerBackgroundDrawable; } else { drawable = Theme.timeBackgroundDrawable; } setDrawableBounds(drawable, timeX - dp(4), layoutHeight - dp(27), timeWidth + dp(8 + (currentMessageObject.isOutOwner() ? 20 : 0)), dp(17)); drawable.draw(canvas); int additionalX = 0; canvas.save(); canvas.translate(timeX + additionalX, layoutHeight - dp(11.3f) - timeLayout.getHeight()); timeLayout.draw(canvas); canvas.restore(); } else { int additionalX = 0; canvas.save(); canvas.translate(timeX + additionalX, layoutHeight - dp(6.5f) - timeLayout.getHeight()); timeLayout.draw(canvas); canvas.restore(); //canvas.drawRect(timeX, layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight(), timeX + availableTimeWidth, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight(), timePaint); } if (currentMessageObject.isOutOwner()) { boolean drawCheck1 = false; boolean drawCheck2 = false; boolean drawClock = false; boolean drawError = false; if (currentMessageObject.isSending()) { drawCheck1 = false; drawCheck2 = false; drawClock = true; drawError = false; } else if (currentMessageObject.isSendError()) { drawCheck1 = false; drawCheck2 = false; drawClock = false; drawError = true; } else if (currentMessageObject.isSent()) { if (!currentMessageObject.isUnread()) { drawCheck1 = true; drawCheck2 = true; } else { drawCheck1 = false; drawCheck2 = true; } drawClock = false; drawError = false; } if (drawClock) { if (!mediaBackground) { setDrawableBounds(Theme.clockDrawable, layoutWidth - dp(18.5f) - Theme.clockDrawable.getIntrinsicWidth(), layoutHeight - dp(8.5f) - Theme.clockDrawable.getIntrinsicHeight()); Theme.clockDrawable.draw(canvas); } else { setDrawableBounds(Theme.clockMediaDrawable, layoutWidth - dp(22.0f) - Theme.clockMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.clockMediaDrawable.getIntrinsicHeight()); Theme.clockMediaDrawable.draw(canvas); } } { if (drawCheck2) { if (!mediaBackground) { if (drawCheck1) { setDrawableBounds(Theme.checkDrawable, layoutWidth - dp(22.5f) - Theme.checkDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.checkDrawable.getIntrinsicHeight()); } else { setDrawableBounds(Theme.checkDrawable, layoutWidth - dp(18.5f) - Theme.checkDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.checkDrawable.getIntrinsicHeight()); } Theme.checkDrawable.draw(canvas); } else { if (drawCheck1) { setDrawableBounds(Theme.checkMediaDrawable, layoutWidth - dp(26.3f) - Theme.checkMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.checkMediaDrawable.getIntrinsicHeight()); } else { setDrawableBounds(Theme.checkMediaDrawable, layoutWidth - dp(21.5f) - Theme.checkMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.checkMediaDrawable.getIntrinsicHeight()); } Theme.checkMediaDrawable.draw(canvas); } } if (drawCheck1) { if (!mediaBackground) { setDrawableBounds(Theme.halfCheckDrawable, layoutWidth - dp(18) - Theme.halfCheckDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.halfCheckDrawable.getIntrinsicHeight()); Theme.halfCheckDrawable.draw(canvas); } else { setDrawableBounds(Theme.halfCheckMediaDrawable, layoutWidth - dp(21.5f) - Theme.halfCheckMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.halfCheckMediaDrawable.getIntrinsicHeight()); Theme.halfCheckMediaDrawable.draw(canvas); } } } if (drawError) { if (!mediaBackground) { setDrawableBounds(Theme.errorDrawable, layoutWidth - dp(18) - Theme.errorDrawable.getIntrinsicWidth(), layoutHeight - dp(7) - Theme.errorDrawable.getIntrinsicHeight()); Theme.errorDrawable.draw(canvas); } else { setDrawableBounds(Theme.errorDrawable, layoutWidth - dp(20.5f) - Theme.errorDrawable.getIntrinsicWidth(), layoutHeight - dp(11.5f) - Theme.errorDrawable.getIntrinsicHeight()); Theme.errorDrawable.draw(canvas); } } } } } public MessageObject getMessageObject() { return currentMessageObject; } }