NekoX/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayoutP...

2395 lines
112 KiB
Java

package org.telegram.ui.Components;
import static org.telegram.messenger.MessageObject.POSITION_FLAG_BOTTOM;
import static org.telegram.messenger.MessageObject.POSITION_FLAG_LEFT;
import static org.telegram.messenger.MessageObject.POSITION_FLAG_RIGHT;
import static org.telegram.messenger.MessageObject.POSITION_FLAG_TOP;
import static org.telegram.ui.Components.UndoView.ACTION_PREVIEW_MEDIA_DESELECTED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadataRetriever;
import android.os.Build;
import android.os.SystemClock;
import android.text.TextPaint;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Interpolator;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.exifinterface.media.ExifInterface;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MediaController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.R;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.VideoEditedInfo;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.ActionBarMenu;
import org.telegram.ui.ActionBar.ActionBarMenuItem;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.ChatActionCell;
import org.telegram.ui.ChatActivity;
import org.telegram.ui.PhotoViewer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAlertLayout {
private final long durationMultiplier = 1;
public float getPreviewScale() {
// preview is 80% of real message size
return AndroidUtilities.displaySize.y > AndroidUtilities.displaySize.x ? .8f : .45f;
}
private ChatActivity.ThemeDelegate themeDelegate;
public RecyclerListView listView;
private LinearLayoutManager layoutManager;
private PreviewGroupsView groupsView;
private UndoView undoView;
public TextView header;
private float draggingCellTouchX = 0, draggingCellTouchY = 0;
private float draggingCellTop = 0, draggingCellLeft = 0;
private float draggingCellFromWidth = 0, draggingCellFromHeight = 0;
private PreviewGroupsView.PreviewGroupCell.MediaCell draggingCell = null;
private boolean draggingCellHiding = false;
private ValueAnimator draggingAnimator;
private float draggingCellGroupY = 0;
private Drawable videoPlayImage;
public ChatAttachAlertPhotoLayoutPreview(ChatAttachAlert alert, Context context, ChatActivity.ThemeDelegate themeDelegate) {
super(alert, context, themeDelegate);
this.themeDelegate = themeDelegate;
setWillNotDraw(false);
ActionBarMenu menu = parentAlert.actionBar.createMenu();
header = new TextView(context);
ActionBarMenuItem dropDownContainer = new ActionBarMenuItem(context, menu, 0, 0, resourcesProvider) {
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setText(header.getText());
}
};
parentAlert.actionBar.addView(dropDownContainer, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, AndroidUtilities.isTablet() ? 64 : 56, 0, 40, 0));
header.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
header.setGravity(Gravity.LEFT);
header.setSingleLine(true);
header.setLines(1);
header.setMaxLines(1);
header.setEllipsize(TextUtils.TruncateAt.END);
header.setTextColor(getThemedColor(Theme.key_dialogTextBlack));
header.setText(LocaleController.getString("AttachMediaPreview", R.string.AttachMediaPreview));
header.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
header.setCompoundDrawablePadding(AndroidUtilities.dp(4));
header.setPadding(0, 0, AndroidUtilities.dp(10), 0);
header.setAlpha(0);
dropDownContainer.addView(header, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 16, 0, 0, 0));
listView = new RecyclerListView(context, resourcesProvider) {
@Override
public void onScrolled(int dx, int dy) {
ChatAttachAlertPhotoLayoutPreview.this.invalidate();
parentAlert.updateLayout(ChatAttachAlertPhotoLayoutPreview.this, true, dy);
groupsView.onScroll();
super.onScrolled(dx, dy);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (draggingCell != null) {
return false;
}
return super.onTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (draggingCell != null) {
return false;
}
return super.onInterceptTouchEvent(ev);
}
};
listView.setAdapter(new RecyclerView.Adapter() {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new RecyclerListView.Holder(groupsView);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
/**/
}
@Override
public int getItemCount() {
return 1;
}
});
listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
listView.setClipChildren(false);
listView.setClipToPadding(false);
listView.setOverScrollMode(RecyclerListView.OVER_SCROLL_NEVER);
listView.setVerticalScrollBarEnabled(false);
listView.setPadding(0, 0, 0, AndroidUtilities.dp(46));
groupsView = new PreviewGroupsView(context);
groupsView.setClipToPadding(true);
groupsView.setClipChildren(true);
addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
this.photoLayout = parentAlert.getPhotoLayout();
groupsView.deletedPhotos.clear();
groupsView.fromPhotoLayout(photoLayout);
undoView = new UndoView(context, null, false, parentAlert.parentThemeDelegate);
undoView.setEnterOffsetMargin(AndroidUtilities.dp(8 + 24));
addView(undoView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 8, 0, 8, 52));
videoPlayImage = context.getResources().getDrawable(R.drawable.play_mini_video);
}
private ViewPropertyAnimator headerAnimator;
private ChatAttachAlertPhotoLayout photoLayout;
private boolean shown = false;
@Override
void onShow(ChatAttachAlert.AttachAlertLayout previousLayout) {
shown = true;
if (previousLayout instanceof ChatAttachAlertPhotoLayout) {
this.photoLayout = (ChatAttachAlertPhotoLayout) previousLayout;
groupsView.deletedPhotos.clear();
groupsView.fromPhotoLayout(photoLayout);
groupsView.requestLayout();
layoutManager.scrollToPositionWithOffset(0, 0);
Runnable setScrollY = () -> {
int currentItemTop = previousLayout.getCurrentItemTop(),
paddingTop = previousLayout.getListTopPadding();
listView.scrollBy(0, (currentItemTop > AndroidUtilities.dp(7) ? paddingTop - currentItemTop : paddingTop));
};
listView.post(setScrollY);
postDelayed(() -> {
if (shown) {
parentAlert.selectedMenuItem.hideSubItem(ChatAttachAlertPhotoLayout.preview);
}
}, 250);
groupsView.toPhotoLayout(photoLayout, false);
} else {
scrollToTop();
}
if (headerAnimator != null) {
headerAnimator.cancel();
}
headerAnimator = header.animate().alpha(1f).setDuration(150).setInterpolator(CubicBezierInterpolator.DEFAULT);
headerAnimator.start();
}
@Override
void onHide() {
shown = false;
if (headerAnimator != null) {
headerAnimator.cancel();
}
headerAnimator = header.animate().alpha(0f).setDuration(150).setInterpolator(CubicBezierInterpolator.EASE_BOTH);
headerAnimator.start();
if (getSelectedItemsCount() > 1) {
parentAlert.selectedMenuItem.showSubItem(ChatAttachAlertPhotoLayout.preview);
}
groupsView.toPhotoLayout(photoLayout, true);
}
@Override
int getSelectedItemsCount() {
return groupsView.getPhotosCount();
}
@Override
void onHidden() {
draggingCell = null;
if (undoView != null) {
undoView.hide(false, 0);
}
}
@Override
int getFirstOffset() {
return getListTopPadding() + AndroidUtilities.dp(56);
}
@Override
boolean shouldHideBottomButtons() {
return true;
}
@Override
void applyCaption(CharSequence text) {
if (photoLayout != null) {
photoLayout.applyCaption(text);
}
}
private static HashMap<MediaController.PhotoEntry, Boolean> photoRotate = new HashMap<>();
private class GroupCalculator {
public ArrayList<MessageObject.GroupedMessagePosition> posArray = new ArrayList<>();
public HashMap<MediaController.PhotoEntry, MessageObject.GroupedMessagePosition> positions = new HashMap<>();
private class MessageGroupedLayoutAttempt {
public int[] lineCounts;
public float[] heights;
public MessageGroupedLayoutAttempt(int i1, int i2, float f1, float f2) {
lineCounts = new int[] {i1, i2};
heights = new float[] {f1, f2};
}
public MessageGroupedLayoutAttempt(int i1, int i2, int i3, float f1, float f2, float f3) {
lineCounts = new int[] {i1, i2, i3};
heights = new float[] {f1, f2, f3};
}
public MessageGroupedLayoutAttempt(int i1, int i2, int i3, int i4, float f1, float f2, float f3, float f4) {
lineCounts = new int[] {i1, i2, i3, i4};
heights = new float[] {f1, f2, f3, f4};
}
}
private float multiHeight(float[] array, int start, int end) {
float sum = 0;
for (int a = start; a < end; a++) {
sum += array[a];
}
return (float) maxSizeWidth / sum;
}
int width, maxX, maxY;
float height;
ArrayList<MediaController.PhotoEntry> photos;
public GroupCalculator(ArrayList<MediaController.PhotoEntry> photos) {
this.photos = photos;
calculate();
}
public void calculate(ArrayList<MediaController.PhotoEntry> photos) {
this.photos = photos;
calculate();
}
private final int maxSizeWidth = 1000; // was 800, made 1000 for preview
public void calculate() {
// TODO: copied from GroupedMessages, would be better to merge
int firstSpanAdditionalSize = 200;
int count = photos.size();
posArray.clear();
positions.clear();
if (count == 0) {
width = 0;
height = 0;
maxX = 0;
maxY = 0;
return;
}
posArray.ensureCapacity(count);
final float maxSizeHeight = 814.0f;
char[] proportionsArray = new char[count];
float averageAspectRatio = 1.0f;
boolean forceCalc = false;
for (int a = 0; a < count; a++) {
MediaController.PhotoEntry photo = photos.get(a);
MessageObject.GroupedMessagePosition position = new MessageObject.GroupedMessagePosition();
position.last = a == count - 1;
int w = photo.cropState != null ? photo.cropState.width : photo.width,
h = photo.cropState != null ? photo.cropState.height : photo.height;
boolean rotate;
if (photoRotate.containsKey(photo)) {
rotate = photoRotate.get(photo);
} else {
rotate = false;
try {
if (photo.isVideo) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
MediaMetadataRetriever m = new MediaMetadataRetriever();
m.setDataSource(photo.path);
String rotation = m.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
rotate = rotation != null && (rotation.equals("90") || rotation.equals("270"));
}
} else {
ExifInterface exif = new ExifInterface(photo.path);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
case ExifInterface.ORIENTATION_ROTATE_270:
rotate = true;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
default:
break;
}
}
} catch (Exception ignore) {}
photoRotate.put(photo, rotate);
}
if (rotate) {
int wasW = w;
w = h;
h = wasW;
}
position.aspectRatio = w / (float) h;
proportionsArray[a] = (
position.aspectRatio > 1.2f ? 'w' : (
position.aspectRatio < .8f ? 'n' : 'q'
)
);
averageAspectRatio += position.aspectRatio;
if (position.aspectRatio > 2.0f) {
forceCalc = true;
}
positions.put(photo, position);
posArray.add(position);
}
String proportions = new String(proportionsArray);
int minHeight = AndroidUtilities.dp(120);
int minWidth = (int) (AndroidUtilities.dp(120) / (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) / (float) maxSizeWidth));
int paddingsWidth = (int) (AndroidUtilities.dp(40) / (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) / (float) maxSizeWidth));
float maxAspectRatio = maxSizeWidth / maxSizeHeight;
averageAspectRatio = averageAspectRatio / count;
float minH = AndroidUtilities.dp(100) / maxSizeHeight;
if (count == 1) {
MessageObject.GroupedMessagePosition position1 = posArray.get(0);
int widthPx = AndroidUtilities.displaySize.x - parentAlert.getBackgroundPaddingLeft() * 2;
float maxHeight = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * .5f;
position1.set(0, 0, 0, 0, 800, (widthPx * .8f) / position1.aspectRatio / maxHeight, POSITION_FLAG_LEFT | POSITION_FLAG_RIGHT | POSITION_FLAG_TOP | POSITION_FLAG_BOTTOM);
} else if (!forceCalc && (count == 2 || count == 3 || count == 4)) {
if (count == 2) {
MessageObject.GroupedMessagePosition position1 = posArray.get(0);
MessageObject.GroupedMessagePosition position2 = posArray.get(1);
if (proportions.equals("ww") && averageAspectRatio > 1.4 * maxAspectRatio && position1.aspectRatio - position2.aspectRatio < 0.2) {
float height = Math.round(Math.min(maxSizeWidth / position1.aspectRatio, Math.min(maxSizeWidth / position2.aspectRatio, maxSizeHeight / 2.0f))) / maxSizeHeight;
position1.set(0, 0, 0, 0, maxSizeWidth, height, POSITION_FLAG_LEFT | POSITION_FLAG_RIGHT | POSITION_FLAG_TOP);
position2.set(0, 0, 1, 1, maxSizeWidth, height, POSITION_FLAG_LEFT | POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM);
} else if (proportions.equals("ww") || proportions.equals("qq")) {
int width = maxSizeWidth / 2;
float height = Math.round(Math.min(width / position1.aspectRatio, Math.min(width / position2.aspectRatio, maxSizeHeight))) / maxSizeHeight;
position1.set(0, 0, 0, 0, width, height, POSITION_FLAG_LEFT | POSITION_FLAG_BOTTOM | POSITION_FLAG_TOP);
position2.set(1, 1, 0, 0, width, height, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM | POSITION_FLAG_TOP);
} else {
int secondWidth = (int) Math.max(0.4f * maxSizeWidth, Math.round((maxSizeWidth / position1.aspectRatio / (1.0f / position1.aspectRatio + 1.0f / position2.aspectRatio))));
int firstWidth = maxSizeWidth - secondWidth;
if (firstWidth < minWidth) {
int diff = minWidth - firstWidth;
firstWidth = minWidth;
secondWidth -= diff;
}
float height = Math.min(maxSizeHeight, Math.round(Math.min(firstWidth / position1.aspectRatio, secondWidth / position2.aspectRatio))) / maxSizeHeight;
position1.set(0, 0, 0, 0, firstWidth, height, POSITION_FLAG_LEFT | POSITION_FLAG_BOTTOM | POSITION_FLAG_TOP);
position2.set(1, 1, 0, 0, secondWidth, height, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM | POSITION_FLAG_TOP);
}
} else if (count == 3) {
MessageObject.GroupedMessagePosition position1 = posArray.get(0);
MessageObject.GroupedMessagePosition position2 = posArray.get(1);
MessageObject.GroupedMessagePosition position3 = posArray.get(2);
if (proportions.charAt(0) == 'n') {
float thirdHeight = Math.min(maxSizeHeight * 0.5f, Math.round(position2.aspectRatio * maxSizeWidth / (position3.aspectRatio + position2.aspectRatio)));
float secondHeight = maxSizeHeight - thirdHeight;
int rightWidth = (int) Math.max(minWidth, Math.min(maxSizeWidth * 0.5f, Math.round(Math.min(thirdHeight * position3.aspectRatio, secondHeight * position2.aspectRatio))));
int leftWidth = Math.round(Math.min(maxSizeHeight * position1.aspectRatio + paddingsWidth, maxSizeWidth - rightWidth));
position1.set(0, 0, 0, 1, leftWidth, 1.0f, POSITION_FLAG_LEFT | POSITION_FLAG_BOTTOM | POSITION_FLAG_TOP);
position2.set(1, 1, 0, 0, rightWidth, secondHeight / maxSizeHeight, POSITION_FLAG_RIGHT | POSITION_FLAG_TOP);
// dkaraush: wtf, why it had minX=0 ?
position3.set(1, 1, 1, 1, rightWidth, thirdHeight / maxSizeHeight, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM);
position3.spanSize = maxSizeWidth;
position1.siblingHeights = new float[] {thirdHeight / maxSizeHeight, secondHeight / maxSizeHeight};
position1.spanSize = maxSizeWidth - rightWidth;
} else {
float firstHeight = Math.round(Math.min(maxSizeWidth / position1.aspectRatio, (maxSizeHeight) * 0.66f)) / maxSizeHeight;
position1.set(0, 1, 0, 0, maxSizeWidth, firstHeight, POSITION_FLAG_LEFT | POSITION_FLAG_RIGHT | POSITION_FLAG_TOP);
int width = maxSizeWidth / 2;
float secondHeight = Math.min(maxSizeHeight - firstHeight, Math.round(Math.min(width / position2.aspectRatio, width / position3.aspectRatio))) / maxSizeHeight;
if (secondHeight < minH) {
secondHeight = minH;
}
position2.set(0, 0, 1, 1, width, secondHeight, POSITION_FLAG_LEFT | POSITION_FLAG_BOTTOM);
position3.set(1, 1, 1, 1, width, secondHeight, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM);
}
} else {
MessageObject.GroupedMessagePosition position1 = posArray.get(0);
MessageObject.GroupedMessagePosition position2 = posArray.get(1);
MessageObject.GroupedMessagePosition position3 = posArray.get(2);
MessageObject.GroupedMessagePosition position4 = posArray.get(3);
if (proportions.charAt(0) == 'w') {
float h0 = Math.round(Math.min(maxSizeWidth / position1.aspectRatio, maxSizeHeight * 0.66f)) / maxSizeHeight;
position1.set(0, 2, 0, 0, maxSizeWidth, h0, POSITION_FLAG_LEFT | POSITION_FLAG_RIGHT | POSITION_FLAG_TOP);
float h = Math.round(maxSizeWidth / (position2.aspectRatio + position3.aspectRatio + position4.aspectRatio));
int w0 = (int) Math.max(minWidth, Math.min(maxSizeWidth * 0.4f, h * position2.aspectRatio));
int w2 = (int) Math.max(Math.max(minWidth, maxSizeWidth * 0.33f), h * position4.aspectRatio);
int w1 = maxSizeWidth - w0 - w2;
if (w1 < AndroidUtilities.dp(58)) {
int diff = AndroidUtilities.dp(58) - w1;
w1 = AndroidUtilities.dp(58);
w0 -= diff / 2;
w2 -= (diff - diff / 2);
}
h = Math.min(maxSizeHeight - h0, h);
h /= maxSizeHeight;
if (h < minH) {
h = minH;
}
position2.set(0, 0, 1, 1, w0, h, POSITION_FLAG_LEFT | POSITION_FLAG_BOTTOM);
position3.set(1, 1, 1, 1, w1, h, POSITION_FLAG_BOTTOM);
position4.set(2, 2, 1, 1, w2, h, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM);
} else {
int w = Math.max(minWidth, Math.round(maxSizeHeight / (1.0f / position2.aspectRatio + 1.0f / position3.aspectRatio + 1.0f / position4.aspectRatio)));
float h0 = Math.min(0.33f, Math.max(minHeight, w / position2.aspectRatio) / maxSizeHeight);
float h1 = Math.min(0.33f, Math.max(minHeight, w / position3.aspectRatio) / maxSizeHeight);
float h2 = 1.0f - h0 - h1;
int w0 = Math.round(Math.min(maxSizeHeight * position1.aspectRatio + paddingsWidth, maxSizeWidth - w));
position1.set(0, 0, 0, 2, w0, h0 + h1 + h2, POSITION_FLAG_LEFT | POSITION_FLAG_TOP | POSITION_FLAG_BOTTOM);
position2.set(1, 1, 0, 0, w, h0, POSITION_FLAG_RIGHT | POSITION_FLAG_TOP);
// dkaraush: wtf, why it had minX=0 ?
position3.set(1, 1, 1, 1, w, h1, POSITION_FLAG_RIGHT);
position3.spanSize = maxSizeWidth;
// dkaraush: wtf, why it had minX=0 ?
position4.set(1, 1, 2, 2, w, h2, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM);
position4.spanSize = maxSizeWidth;
position1.spanSize = maxSizeWidth - w;
position1.siblingHeights = new float[] {h0, h1, h2};
}
}
} else {
float[] croppedRatios = new float[posArray.size()];
for (int a = 0; a < count; a++) {
if (averageAspectRatio > 1.1f) {
croppedRatios[a] = Math.max(1.0f, posArray.get(a).aspectRatio);
} else {
croppedRatios[a] = Math.min(1.0f, posArray.get(a).aspectRatio);
}
croppedRatios[a] = Math.max(0.66667f, Math.min(1.7f, croppedRatios[a]));
}
int firstLine;
int secondLine;
int thirdLine;
int fourthLine;
ArrayList<MessageGroupedLayoutAttempt> attempts = new ArrayList<>();
for (firstLine = 1; firstLine < croppedRatios.length; firstLine++) {
secondLine = croppedRatios.length - firstLine;
if (firstLine > 3 || secondLine > 3) {
continue;
}
attempts.add(new MessageGroupedLayoutAttempt(firstLine, secondLine, multiHeight(croppedRatios, 0, firstLine), multiHeight(croppedRatios, firstLine, croppedRatios.length)));
}
for (firstLine = 1; firstLine < croppedRatios.length - 1; firstLine++) {
for (secondLine = 1; secondLine < croppedRatios.length - firstLine; secondLine++) {
thirdLine = croppedRatios.length - firstLine - secondLine;
if (firstLine > 3 || secondLine > (averageAspectRatio < 0.85f ? 4 : 3) || thirdLine > 3) {
continue;
}
attempts.add(new MessageGroupedLayoutAttempt(firstLine, secondLine, thirdLine, multiHeight(croppedRatios, 0, firstLine), multiHeight(croppedRatios, firstLine, firstLine + secondLine), multiHeight(croppedRatios, firstLine + secondLine, croppedRatios.length)));
}
}
for (firstLine = 1; firstLine < croppedRatios.length - 2; firstLine++) {
for (secondLine = 1; secondLine < croppedRatios.length - firstLine; secondLine++) {
for (thirdLine = 1; thirdLine < croppedRatios.length - firstLine - secondLine; thirdLine++) {
fourthLine = croppedRatios.length - firstLine - secondLine - thirdLine;
if (firstLine > 3 || secondLine > 3 || thirdLine > 3 || fourthLine > 3) {
continue;
}
attempts.add(new MessageGroupedLayoutAttempt(firstLine, secondLine, thirdLine, fourthLine, multiHeight(croppedRatios, 0, firstLine), multiHeight(croppedRatios, firstLine, firstLine + secondLine), multiHeight(croppedRatios, firstLine + secondLine, firstLine + secondLine + thirdLine), multiHeight(croppedRatios, firstLine + secondLine + thirdLine, croppedRatios.length)));
}
}
}
MessageGroupedLayoutAttempt optimal = null;
float optimalDiff = 0.0f;
float maxHeight = maxSizeWidth / 3 * 4;
for (int a = 0; a < attempts.size(); a++) {
MessageGroupedLayoutAttempt attempt = attempts.get(a);
float height = 0;
float minLineHeight = Float.MAX_VALUE;
for (int b = 0; b < attempt.heights.length; b++){
height += attempt.heights[b];
if (attempt.heights[b] < minLineHeight) {
minLineHeight = attempt.heights[b];
}
}
float diff = Math.abs(height - maxHeight);
if (attempt.lineCounts.length > 1) {
if (attempt.lineCounts[0] > attempt.lineCounts[1] || (attempt.lineCounts.length > 2 && attempt.lineCounts[1] > attempt.lineCounts[2]) || (attempt.lineCounts.length > 3 && attempt.lineCounts[2] > attempt.lineCounts[3])) {
diff *= 1.2f;
}
}
if (minLineHeight < minWidth) {
diff *= 1.5f;
}
if (optimal == null || diff < optimalDiff) {
optimal = attempt;
optimalDiff = diff;
}
}
if (optimal == null) {
return;
}
int index = 0;
for (int i = 0; i < optimal.lineCounts.length; i++) {
int c = optimal.lineCounts[i];
float lineHeight = optimal.heights[i];
int spanLeft = maxSizeWidth;
MessageObject.GroupedMessagePosition posToFix = null;
for (int k = 0; k < c; k++) {
float ratio = croppedRatios[index];
int width = (int) (ratio * lineHeight);
spanLeft -= width;
MessageObject.GroupedMessagePosition pos = posArray.get(index);
int flags = 0;
if (i == 0) {
flags |= POSITION_FLAG_TOP;
}
if (i == optimal.lineCounts.length - 1) {
flags |= POSITION_FLAG_BOTTOM;
}
if (k == 0) {
flags |= POSITION_FLAG_LEFT;
posToFix = pos;
}
if (k == c - 1) {
flags |= POSITION_FLAG_RIGHT;
posToFix = pos;
}
pos.set(k, k, i, i, width, Math.max(minH, lineHeight / maxSizeHeight), flags);
index++;
}
if (posToFix != null) {
posToFix.pw += spanLeft;
posToFix.spanSize += spanLeft;
}
}
}
for (int a = 0; a < count; a++) {
MessageObject.GroupedMessagePosition pos = posArray.get(a);
if (pos.minX == 0) {
pos.spanSize += firstSpanAdditionalSize;
}
if ((pos.flags & POSITION_FLAG_RIGHT) != 0) {
pos.edge = true;
}
maxX = Math.max(maxX, pos.maxX);
maxY = Math.max(maxY, pos.maxY);
pos.left = getLeft(pos, pos.minY, pos.maxY, pos.minX);
}
for (int a = 0; a < count; ++a) {
MessageObject.GroupedMessagePosition pos = posArray.get(a);
pos.top = getTop(pos, pos.minY);
}
width = getWidth();
height = getHeight();
}
public int getWidth() {
int[] lineWidths = new int[10];
Arrays.fill(lineWidths, 0);
final int count = posArray.size();
for (int i = 0; i < count; ++i) {
MessageObject.GroupedMessagePosition pos = posArray.get(i);
int width = pos.pw;
for (int y = pos.minY; y <= pos.maxY; ++y) {
lineWidths[y] += width;
}
}
int width = lineWidths[0];
for (int y = 1; y < lineWidths.length; ++y) {
if (width < lineWidths[y]) {
width = lineWidths[y];
}
}
return width;
}
public float getHeight() {
float[] lineHeights = new float[10];
Arrays.fill(lineHeights, 0f);
final int count = posArray.size();
for (int i = 0; i < count; ++i) {
MessageObject.GroupedMessagePosition pos = posArray.get(i);
float height = pos.ph;
for (int x = pos.minX; x <= pos.maxX; ++x) {
lineHeights[x] += height;
}
}
float height = lineHeights[0];
for (int y = 1; y < lineHeights.length; ++y) {
if (height < lineHeights[y]) {
height = lineHeights[y];
}
}
return height;
}
private float getLeft(MessageObject.GroupedMessagePosition except, int minY, int maxY, int minX) {
float[] sums = new float[maxY - minY + 1];
Arrays.fill(sums, 0f);
final int count = posArray.size();
for (int i = 0; i < count; ++i) {
MessageObject.GroupedMessagePosition pos = posArray.get(i);
if (pos != except && pos.maxX < minX) {
final int end = Math.min(pos.maxY, maxY) - minY;
for (int y = Math.max(pos.minY - minY, 0); y <= end; ++y) {
sums[y] += pos.pw;
}
}
}
float max = 0;
for (int i = 0; i < sums.length; ++i) {
if (max < sums[i]) {
max = sums[i];
}
}
return max;
}
private float getTop(MessageObject.GroupedMessagePosition except, int minY) {
float[] sums = new float[maxX + 1];
Arrays.fill(sums, 0f);
final int count = posArray.size();
for (int i = 0; i < count; ++i) {
MessageObject.GroupedMessagePosition pos = posArray.get(i);
if (pos != except && pos.maxY < minY) {
for (int x = pos.minX; x <= pos.maxX; ++x) {
sums[x] += pos.ph;
}
}
}
float max = 0;
for (int i = 0; i < sums.length; ++i) {
if (max < sums[i]) {
max = sums[i];
}
}
return max;
}
}
@Override
int getListTopPadding() {
return listView.getPaddingTop()/* + AndroidUtilities.dp(9)*/;
}
@Override
int getCurrentItemTop() {
if (listView.getChildCount() <= 0) {
listView.setTopGlowOffset(listView.getPaddingTop());
return Integer.MAX_VALUE;
}
View child = listView.getChildAt(0);
RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child);
int top = child.getTop();
int newOffset = AndroidUtilities.dp(8);
if (top >= AndroidUtilities.dp(8) && holder != null && holder.getAdapterPosition() == 0) {
newOffset = top;
}
listView.setTopGlowOffset(newOffset);
return newOffset;
}
private int paddingTop;
@Override
public void onPreMeasure(int availableWidth, int availableHeight) {
ignoreLayout = true;
LayoutParams layoutParams = (LayoutParams) getLayoutParams();
layoutParams.topMargin = ActionBar.getCurrentActionBarHeight();
if (!AndroidUtilities.isTablet() && AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) {
paddingTop = (int) (availableHeight / 3.5f);
} else {
paddingTop = (availableHeight / 5 * 2);
}
paddingTop -= AndroidUtilities.dp(52);
if (paddingTop < 0) {
paddingTop = 0;
}
// paddingTop -= AndroidUtilities.dp(9);
if (listView.getPaddingTop() != paddingTop) {
listView.setPadding(listView.getPaddingLeft(), paddingTop, listView.getPaddingRight(), listView.getPaddingBottom());
invalidate();
}
header.setTextSize(!AndroidUtilities.isTablet() && AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y ? 18 : 20);
ignoreLayout = false;
}
@Override
void scrollToTop() {
// scrollView.smoothScrollTo(0, 0);
listView.smoothScrollToPosition(0);
}
@Override
int needsActionBar() {
return 1;
}
@Override
boolean onBackPressed() {
parentAlert.updatePhotoPreview(false);
return true;
}
private boolean ignoreLayout = false;
@Override
public void requestLayout() {
if (ignoreLayout) {
return;
}
super.requestLayout();
}
@Override
void onMenuItemClick(int id) {
try {
parentAlert.getPhotoLayout().onMenuItemClick(id);
} catch (Exception ignore) {}
}
@Override
protected void dispatchDraw(Canvas canvas) {
boolean restore = false;
if (parentAlert.parentThemeDelegate != null) {
Drawable chatBackgroundDrawable = parentAlert.parentThemeDelegate.getWallpaperDrawable();
if (chatBackgroundDrawable != null) {
int paddingTop = getCurrentItemTop();
int finalMove;
if (AndroidUtilities.isTablet()) {
finalMove = 16;
} else if (AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) {
finalMove = 6;
} else {
finalMove = 12;
}
if (paddingTop < ActionBar.getCurrentActionBarHeight()) {
paddingTop -= AndroidUtilities.dp((1f - paddingTop / (float) ActionBar.getCurrentActionBarHeight()) * finalMove);
}
paddingTop = Math.max(0, paddingTop);
canvas.save();
canvas.clipRect(0, paddingTop, getWidth(), getHeight());
chatBackgroundDrawable.setBounds(0, paddingTop, getWidth(), paddingTop + AndroidUtilities.displaySize.y);
chatBackgroundDrawable.draw(canvas);
restore = true;
}
}
super.dispatchDraw(canvas);
if (restore) {
canvas.restore();
}
}
private boolean isPortrait = AndroidUtilities.displaySize.y > AndroidUtilities.displaySize.x;
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
boolean isPortrait = AndroidUtilities.displaySize.y > AndroidUtilities.displaySize.x;
if (this.isPortrait != isPortrait) {
this.isPortrait = isPortrait;
final int groupCellsCount = groupsView.groupCells.size();
for (int i = 0; i < groupCellsCount; ++i) {
PreviewGroupsView.PreviewGroupCell groupCell = groupsView.groupCells.get(i);
if (groupCell.group.photos.size() == 1) {
groupCell.setGroup(groupCell.group, true);
}
}
}
}
@Override
void onSelectedItemsCountChanged(int count) {
if (count > 1) {
parentAlert.selectedMenuItem.showSubItem(ChatAttachAlertPhotoLayout.group);
} else {
parentAlert.selectedMenuItem.hideSubItem(ChatAttachAlertPhotoLayout.group);
}
}
private class PreviewGroupsView extends ViewGroup {
private ChatActionCell hintView;
public PreviewGroupsView(Context context) {
super(context);
setWillNotDraw(false);
hintView = new ChatActionCell(context, true, themeDelegate);
hintView.setCustomText(LocaleController.getString("AttachMediaDragHint", R.string.AttachMediaDragHint));
addView(hintView);
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
hintView.layout(0, 0, hintView.getMeasuredWidth(), hintView.getMeasuredHeight());
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return false;
}
private ArrayList<PreviewGroupCell> groupCells = new ArrayList<>();
private HashMap<Object, Object> deletedPhotos = new HashMap<>();
public void saveDeletedImageId(MediaController.PhotoEntry photo) {
if (photoLayout == null) {
return;
}
HashMap<Object, Object> photosMap = photoLayout.getSelectedPhotos();
List<Map.Entry<Object, Object>> entries = new ArrayList<>(photosMap.entrySet());
final int entriesCount = entries.size();
for (int i = 0; i < entriesCount; ++i) {
if (entries.get(i).getValue() == photo) {
deletedPhotos.put(photo, entries.get(i).getKey());
break;
}
}
}
public void fromPhotoLayout(ChatAttachAlertPhotoLayout photoLayout) {
photosOrder = photoLayout.getSelectedPhotosOrder();
photosMap = photoLayout.getSelectedPhotos();
fromPhotoArrays();
}
public void fromPhotoArrays() {
groupCells.clear();
ArrayList<MediaController.PhotoEntry> photos = new ArrayList<>();
final int photosOrderSize = photosOrder.size(),
photosOrderLast = photosOrderSize - 1;
for (int i = 0; i < photosOrderSize; ++i) {
int imageId = (Integer) photosOrder.get(i);
photos.add((MediaController.PhotoEntry) photosMap.get(imageId));
if (i % 10 == 9 || i == photosOrderLast) {
PreviewGroupCell groupCell = new PreviewGroupCell();
groupCell.setGroup(new GroupCalculator(photos), false);
groupCells.add(groupCell);
photos = new ArrayList<>();
}
}
}
HashMap<Object, Object> photosMap;
List<Map.Entry<Object, Object>> photosMapKeys;
HashMap<Object, Object> selectedPhotos;
ArrayList<Object> photosOrder;
public void calcPhotoArrays() {
photosMap = photoLayout.getSelectedPhotos();
photosMapKeys = new ArrayList<>(photosMap.entrySet());
selectedPhotos = new HashMap<>();
photosOrder = new ArrayList<>();
final int groupCellsCount = groupCells.size();
for (int i = 0; i < groupCellsCount; ++i) {
PreviewGroupCell groupCell = groupCells.get(i);
GroupCalculator group = groupCell.group;
if (group.photos.size() == 0) {
continue;
}
final int photosCount = group.photos.size();
for (int j = 0; j < photosCount; ++j) {
MediaController.PhotoEntry photoEntry = group.photos.get(j);
if (deletedPhotos.containsKey(photoEntry)) {
Object imageId = deletedPhotos.get(photoEntry);
selectedPhotos.put(imageId, photoEntry);
photosOrder.add(imageId);
} else {
boolean found = false;
for (int k = 0; k < photosMapKeys.size(); ++k) {
Map.Entry<Object, Object> entry = photosMapKeys.get(k);
Object value = entry.getValue();
if (value == photoEntry) {
Object key = entry.getKey();
selectedPhotos.put(key, value);
photosOrder.add(key);
found = true;
break;
}
}
if (!found) {
for (int k = 0; k < photosMapKeys.size(); ++k) {
Map.Entry<Object, Object> entry = photosMapKeys.get(k);
Object value = entry.getValue();
if (
value instanceof MediaController.PhotoEntry &&
((MediaController.PhotoEntry) value).path != null &&
photoEntry != null &&
((MediaController.PhotoEntry) value).path.equals(photoEntry.path)
) {
Object key = entry.getKey();
selectedPhotos.put(key, value);
photosOrder.add(key);
break;
}
}
}
}
}
}
}
public void toPhotoLayout(ChatAttachAlertPhotoLayout photoLayout, boolean updateLayout) {
int previousCount = photoLayout.getSelectedPhotosOrder().size();
calcPhotoArrays();
photoLayout.updateSelected(selectedPhotos, photosOrder, updateLayout);
if (previousCount != photosOrder.size()) {
parentAlert.updateCountButton(1);
}
}
public int getPhotosCount() {
int count = 0;
final int groupCellsCount = groupCells.size();
for (int i = 0; i < groupCellsCount; ++i) {
PreviewGroupCell groupCell = groupCells.get(i);
if (groupCell != null && groupCell.group != null && groupCell.group.photos != null) {
count += groupCell.group.photos.size();
}
}
return count;
}
public ArrayList<MediaController.PhotoEntry> getPhotos() {
ArrayList<MediaController.PhotoEntry> photos = new ArrayList<>();
final int groupCellsCount = groupCells.size();
for (int i = 0; i < groupCellsCount; ++i) {
PreviewGroupCell groupCell = groupCells.get(i);
if (groupCell != null && groupCell.group != null && groupCell.group.photos != null) {
photos.addAll(groupCell.group.photos);
}
}
return photos;
}
private int paddingTop = AndroidUtilities.dp(8 + 8);
private int paddingBottom = AndroidUtilities.dp(32 + 32);
private int lastMeasuredHeight = 0;
private int measurePureHeight() {
int height = paddingTop + paddingBottom;
final int groupCellsCount = groupCells.size();
for (int i = 0; i < groupCellsCount; ++i) {
height += groupCells.get(i).measure();
}
if (hintView.getMeasuredHeight() <= 0) {
hintView.measure(
MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.x, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(9999, MeasureSpec.AT_MOST)
);
}
height += hintView.getMeasuredHeight();
return height;
}
private int measureHeight() {
return Math.max(measurePureHeight(), AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(8 + 46 - 9));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
hintView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(9999, MeasureSpec.AT_MOST));
if (lastMeasuredHeight <= 0) {
lastMeasuredHeight = measureHeight();
}
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Math.max(MeasureSpec.getSize(heightMeasureSpec), lastMeasuredHeight), MeasureSpec.EXACTLY));
}
@Override
public void invalidate() {
int measuredHeight = measureHeight();
if (lastMeasuredHeight != measuredHeight) {
lastMeasuredHeight = measuredHeight;
requestLayout();
}
super.invalidate();
}
float viewTop, viewBottom;
boolean[] lastGroupSeen = null;
private boolean[] groupSeen() {
boolean[] seen = new boolean[groupCells.size()];
float y = paddingTop;
int scrollY = listView.computeVerticalScrollOffset();
viewTop = Math.max(0, scrollY - getListTopPadding());
viewBottom = listView.getMeasuredHeight() - getListTopPadding() + scrollY;
final int groupCellsSize = groupCells.size();
for (int i = 0; i < groupCellsSize; ++i) {
PreviewGroupCell groupCell = groupCells.get(i);
float height = groupCell.measure();
seen[i] = isSeen(y, y + height);
y += height;
}
return seen;
}
public boolean isSeen(float fromY, float toY) {
return (fromY >= viewTop && fromY <= viewBottom) || (toY >= viewTop && toY <= viewBottom) || (fromY <= viewTop && toY >= viewBottom);
}
public void onScroll() {
boolean newGroupSeen = lastGroupSeen == null;
if (!newGroupSeen) {
boolean[] seen = groupSeen();
if (seen.length != lastGroupSeen.length) {
newGroupSeen = true;
} else {
for (int i = 0; i < seen.length; ++i) {
if (seen[i] != lastGroupSeen[i]) {
newGroupSeen = true;
break;
}
}
}
} else {
lastGroupSeen = groupSeen();
}
if (newGroupSeen) {
invalidate();
}
}
public void remeasure() {
float y = paddingTop;
int i = 0;
final int groupCellsCount = groupCells.size();
for (int j = 0; j < groupCellsCount; ++j) {
PreviewGroupCell groupCell = groupCells.get(j);
float height = groupCell.measure();
groupCell.y = y;
groupCell.indexStart = i;
y += height;
i += groupCell.group.photos.size();
}
}
@Override
public void onDraw(Canvas canvas) {
float y = paddingTop;
int i = 0;
int scrollY = listView.computeVerticalScrollOffset();
viewTop = Math.max(0, scrollY - getListTopPadding());
viewBottom = listView.getMeasuredHeight() - getListTopPadding() + scrollY;
canvas.save();
canvas.translate(0, paddingTop);
final int groupCellsCount = groupCells.size();
for (int j = 0; j < groupCellsCount; ++j) {
PreviewGroupCell groupCell = groupCells.get(j);
float height = groupCell.measure();
groupCell.y = y;
groupCell.indexStart = i;
boolean groupIsSeen = (y >= viewTop && y <= viewBottom) || (y+height >= viewTop && y+height <= viewBottom) || (y <= viewTop && y+height >= viewBottom);
if (groupIsSeen && groupCell.draw(canvas)) {
invalidate();
}
canvas.translate(0, height);
y += height;
i += groupCell.group.photos.size();
}
hintView.setVisiblePart(y, hintView.getMeasuredHeight());
if (hintView.hasGradientService()) {
hintView.drawBackground(canvas, true);
}
hintView.draw(canvas);
canvas.restore();
if (draggingCell != null) {
canvas.save();
Point point = dragTranslate();
canvas.translate(point.x, point.y);
if (draggingCell.draw(canvas, true)) {
invalidate();
}
canvas.restore();
}
super.onDraw(canvas);
}
long tapTime = 0;
PreviewGroupCell tapGroupCell = null;
PreviewGroupCell.MediaCell tapMediaCell = null;
private float draggingT = 0;
private float savedDragFromX, savedDragFromY, savedDraggingT;
private final Point tmpPoint = new Point();
Point dragTranslate() {
if (draggingCell == null) {
tmpPoint.x = 0;
tmpPoint.y = 0;
return tmpPoint;
}
if (!draggingCellHiding) {
RectF drawingRect = draggingCell.rect();
RectF finalDrawingRect = draggingCell.rect(1f);
tmpPoint.x = AndroidUtilities.lerp(
finalDrawingRect.left + drawingRect.width() / 2f,
draggingCellTouchX - (draggingCellLeft - .5f) * draggingCellFromWidth,
draggingT
);
tmpPoint.y = AndroidUtilities.lerp(
draggingCell.groupCell.y + finalDrawingRect.top + drawingRect.height() / 2f,
draggingCellTouchY - (draggingCellTop - .5f) * draggingCellFromHeight + draggingCellGroupY,
draggingT
);
} else {
RectF drawingRect = draggingCell.rect();
RectF finalDrawingRect = draggingCell.rect(1f);
tmpPoint.x = AndroidUtilities.lerp(
finalDrawingRect.left + drawingRect.width() / 2f,
savedDragFromX,
draggingT / savedDraggingT
);
tmpPoint.y = AndroidUtilities.lerp(
draggingCell.groupCell.y + finalDrawingRect.top + drawingRect.height() / 2f,
savedDragFromY,
draggingT / savedDraggingT
);
}
return tmpPoint;
}
void stopDragging() {
if (draggingAnimator != null) {
draggingAnimator.cancel();
}
Point dragTranslate = dragTranslate();
savedDraggingT = draggingT;
savedDragFromX = dragTranslate.x;
savedDragFromY = dragTranslate.y;
draggingCellHiding = true;
draggingAnimator = ValueAnimator.ofFloat(savedDraggingT, 0f);
draggingAnimator.addUpdateListener(a -> {
draggingT = (float) a.getAnimatedValue();
invalidate();
});
draggingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
draggingCell = null;
draggingCellHiding = false;
invalidate();
}
});
draggingAnimator.setDuration((long) (200 * durationMultiplier));
draggingAnimator.start();
invalidate();
}
void startDragging(PreviewGroupCell.MediaCell cell) {
draggingCell = cell;
draggingCellGroupY = draggingCell.groupCell.y;
draggingCellHiding = false;
draggingT = 0;
invalidate();
if (draggingAnimator != null) {
draggingAnimator.cancel();
}
draggingAnimator = ValueAnimator.ofFloat(0, 1f);
draggingAnimator.addUpdateListener(a -> {
draggingT = (float) a.getAnimatedValue();
invalidate();
});
draggingAnimator.setDuration((long) (200 * durationMultiplier));
draggingAnimator.start();
}
private boolean scrollerStarted = false;
private final Runnable scroller = new Runnable() {
@Override
public void run() {
if (draggingCell == null || draggingCellHiding) {
return;
}
int scrollY = listView.computeVerticalScrollOffset();
boolean atBottom = scrollY + listView.computeVerticalScrollExtent() >= (measurePureHeight() - paddingBottom + paddingTop);
float top = Math.max(0, draggingCellTouchY - Math.max(0, scrollY - getListTopPadding()) - AndroidUtilities.dp(52));
float bottom = Math.max(0, (listView.getMeasuredHeight() - (draggingCellTouchY - scrollY) - getListTopPadding()) - AndroidUtilities.dp(52 + 32));
final float r = AndroidUtilities.dp(32);
float dy = 0;
if (top < r && scrollY > getListTopPadding()) {
dy = -(1f - top / r) * (float) AndroidUtilities.dp(6);
} else if (bottom < r) {
dy = (1f - bottom / r) * (float) AndroidUtilities.dp(6);
}
if (Math.abs((int) dy) > 0 && listView.canScrollVertically((int) dy) && !(dy > 0 && atBottom)) {
draggingCellTouchY += dy;
listView.scrollBy(0, (int) dy);
invalidate();
}
scrollerStarted = true;
postDelayed(this, 15);
}
};
/*
*
*
if (getSelectedItemsCount() > 1) {
// short tap -> remove photo
final MediaController.PhotoEntry photo = tapMediaCell.photoEntry;
final int index = tapGroupCell.group.photos.indexOf(photo);
if (index >= 0) {
saveDeletedImageId(photo);
final PreviewGroupCell groupCell = tapGroupCell;
groupCell.group.photos.remove(index);
groupCell.setGroup(groupCell.group, true);
updateGroups();
toPhotoLayout(photoLayout, false);
final int currentUndoViewId = ++undoViewId;
undoView.showWithAction(0, ACTION_PREVIEW_MEDIA_DESELECTED, photo, null, () -> {
if (draggingAnimator != null) {
draggingAnimator.cancel();
}
draggingCell = null;
draggingT = 0;
pushToGroup(groupCell, photo, index);
updateGroups();
toPhotoLayout(photoLayout, false);
});
postDelayed(() -> {
if (currentUndoViewId == undoViewId && undoView.isShown()) {
undoView.hide(true, 1);
}
}, 1000 * 4);
}
if (draggingAnimator != null) {
draggingAnimator.cancel();
}
}
* */
GroupingPhotoViewerProvider photoViewerProvider = new GroupingPhotoViewerProvider();
class GroupingPhotoViewerProvider extends PhotoViewer.EmptyPhotoViewerProvider {
private ArrayList<MediaController.PhotoEntry> photos = new ArrayList<>();
public void init(ArrayList<MediaController.PhotoEntry> photos) {
this.photos = photos;
}
@Override
public void onClose() {
fromPhotoArrays();
toPhotoLayout(photoLayout, false);
}
@Override
public boolean isPhotoChecked(int index) {
if (index < 0 || index >= photos.size()) {
return false;
}
return photosOrder.contains((Integer) photos.get(index).imageId);
}
@Override
public int setPhotoChecked(int index, VideoEditedInfo videoEditedInfo) {
if (index < 0 || index >= photos.size()) {
return -1;
}
Object imageId = photos.get(index).imageId;
int orderIndex = photosOrder.indexOf((Integer) imageId);
if (orderIndex >= 0) {
if (photosOrder.size() <= 1) {
return -1;
}
photosOrder.remove(orderIndex);
fromPhotoArrays();
return orderIndex;
} else {
photosOrder.add(imageId);
fromPhotoArrays();
return photosOrder.size() - 1;
}
}
@Override
public int setPhotoUnchecked(Object entry) {
MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) entry;
Object imageId = photoEntry.imageId;
if (photosOrder.size() <= 1) {
return -1;
}
int index = photosOrder.indexOf((Integer) imageId);
if (index >= 0) {
photosOrder.remove(index);
fromPhotoArrays();
return index;
}
return -1;
}
@Override
public int getSelectedCount() {
return photosOrder.size();
}
@Override
public ArrayList<Object> getSelectedPhotosOrder() {
return photosOrder;
}
@Override
public HashMap<Object, Object> getSelectedPhotos() {
return photosMap;
}
@Override
public int getPhotoIndex(int index) {
if (index < 0 || index >= photos.size()) {
return -1;
}
MediaController.PhotoEntry photoEntry = photos.get(index);
if (photoEntry == null) {
return -1;
}
return photosOrder.indexOf(photoEntry.imageId);
}
@Override
public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index, boolean needPreview) {
if (index < 0 || index >= photos.size() || !isPhotoChecked(index)) {
return null;
}
MediaController.PhotoEntry photoEntry = photos.get(index);
if (photoEntry != null) {
PreviewGroupCell group = null;
PreviewGroupCell.MediaCell mediaCell = null;
final int groupCellsCount = groupCells.size();
for (int i = 0; i < groupCellsCount; ++i) {
group = groupCells.get(i);
if (group != null && group.media != null) {
final int count = group.media.size();
for (int j = 0; j < count; ++j) {
PreviewGroupCell.MediaCell cell = group.media.get(j);
if (cell != null && cell.photoEntry == photoEntry && cell.scale > .5) {
mediaCell = group.media.get(j);
break;
}
}
if (mediaCell != null) {
break;
}
}
}
if (group != null && mediaCell != null) {
PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject();
int[] coords = new int[2];
getLocationInWindow(coords);
if (Build.VERSION.SDK_INT < 26) {
coords[0] -= parentAlert.getLeftInset();
}
object.viewX = coords[0];
object.viewY = coords[1] + (int) group.y;
object.scale = 1;
object.parentView = PreviewGroupsView.this;
object.imageReceiver = mediaCell.image;
object.thumb = object.imageReceiver.getBitmapSafe();
object.radius = new int[4];
object.radius[0] = (int) mediaCell.roundRadiuses.left;
object.radius[1] = (int) mediaCell.roundRadiuses.top;
object.radius[2] = (int) mediaCell.roundRadiuses.right;
object.radius[3] = (int) mediaCell.roundRadiuses.bottom;
object.clipTopAddition = (int) (-PreviewGroupsView.this.getY());
object.clipBottomAddition = PreviewGroupsView.this.getHeight() - (int) (-PreviewGroupsView.this.getY() + listView.getHeight() - parentAlert.getClipLayoutBottom());
return object;
}
}
return null;
}
@Override
public boolean cancelButtonPressed() {
return false;
}
@Override
public void updatePhotoAtIndex(int index) {
if (index < 0 || index >= photos.size()) {
return;
}
MediaController.PhotoEntry photoEntry = photos.get(index);
if (photoEntry == null) {
return;
}
int imageId = photoEntry.imageId;
invalidate();
for (int i = 0; i < groupCells.size(); ++i) {
PreviewGroupCell groupCell = groupCells.get(i);
if (groupCell != null && groupCell.media != null) {
for (int j = 0; j < groupCell.media.size(); ++j) {
PreviewGroupCell.MediaCell mediaCell = groupCell.media.get(j);
if (mediaCell != null && mediaCell.photoEntry.imageId == imageId) {
mediaCell.setImage(photoEntry);
}
}
boolean hadUpdates = false;
if (groupCell.group != null && groupCell.group.photos != null) {
for (int j = 0; j < groupCell.group.photos.size(); ++j) {
if (groupCell.group.photos.get(j).imageId == imageId) {
groupCell.group.photos.set(j, photoEntry);
hadUpdates = true;
}
}
}
if (hadUpdates) {
groupCell.setGroup(groupCell.group, true);
}
}
}
remeasure();
invalidate();
}
};
private int undoViewId = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = false;
float touchX = event.getX(),
touchY = event.getY();
PreviewGroupCell touchGroupCell = null;
PreviewGroupCell.MediaCell touchMediaCell = null;
float groupY = 0;
final int groupCellsCount = groupCells.size();
for (int j = 0; j < groupCellsCount; ++j) {
PreviewGroupCell groupCell = groupCells.get(j);
float height = groupCell.measure();
if (touchY >= groupY && touchY <= groupY + height) {
touchGroupCell = groupCell;
break;
}
groupY += height;
}
if (touchGroupCell != null) {
final int mediaCount = touchGroupCell.media.size();
for (int i = 0; i < mediaCount; ++i) {
PreviewGroupCell.MediaCell mediaCell = touchGroupCell.media.get(i);
if (mediaCell != null && mediaCell.drawingRect().contains(touchX, touchY - groupY)) {
touchMediaCell = mediaCell;
break;
}
}
}
PreviewGroupCell draggingOverGroupCell = null;
PreviewGroupCell.MediaCell draggingOverMediaCell = null;
if (draggingCell != null) {
groupY = 0;
RectF drawingRect = draggingCell.rect();
Point dragPoint = dragTranslate();
RectF draggingCellXY = new RectF();
float cx = dragPoint.x, cy = dragPoint.y;
draggingCellXY.set(
cx - drawingRect.width() / 2,
cy - drawingRect.height() / 2,
cx + drawingRect.width() / 2,
cy + drawingRect.height() / 2
);
float maxLength = 0;
for (int j = 0; j < groupCellsCount; ++j) {
PreviewGroupCell groupCell = groupCells.get(j);
float height = groupCell.measure();
float top = groupY, bottom = groupY + height;
if (bottom >= draggingCellXY.top && draggingCellXY.bottom >= top) {
float length = Math.min(bottom, draggingCellXY.bottom) - Math.max(top, draggingCellXY.top);
if (length > maxLength) {
draggingOverGroupCell = groupCell;
maxLength = length;
}
}
groupY += height;
}
if (draggingOverGroupCell != null) {
float maxArea = 0;
final int mediaCount = draggingOverGroupCell.media.size();
for (int i = 0; i < mediaCount; ++i) {
PreviewGroupCell.MediaCell mediaCell = draggingOverGroupCell.media.get(i);
if (mediaCell != null && mediaCell != draggingCell && draggingOverGroupCell.group.photos.contains(mediaCell.photoEntry)) {
RectF mediaCellRect = mediaCell.drawingRect();
if ((mediaCell.positionFlags & POSITION_FLAG_TOP) > 0) {
mediaCellRect.top = 0;
}
if ((mediaCell.positionFlags & POSITION_FLAG_LEFT) > 0) {
mediaCellRect.left = 0;
}
if ((mediaCell.positionFlags & POSITION_FLAG_RIGHT) > 0) {
mediaCellRect.right = getWidth();
}
if ((mediaCell.positionFlags & POSITION_FLAG_BOTTOM) > 0) {
mediaCellRect.bottom = draggingOverGroupCell.height;
}
if (RectF.intersects(draggingCellXY, mediaCellRect)) {
float area = (
(Math.min(mediaCellRect.right, draggingCellXY.right) - Math.max(mediaCellRect.left, draggingCellXY.left)) *
(Math.min(mediaCellRect.bottom, draggingCellXY.bottom) - Math.max(mediaCellRect.top, draggingCellXY.top))
) / (draggingCellXY.width() * draggingCellXY.height());
if (area > 0.15f && area > maxArea) {
draggingOverMediaCell = mediaCell;
maxArea = area;
}
}
}
}
}
}
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN && draggingCell == null && !listView.scrollingByUser && (draggingAnimator == null || !draggingAnimator.isRunning()) && touchGroupCell != null && touchMediaCell != null && touchGroupCell.group != null && touchGroupCell.group.photos.contains(touchMediaCell.photoEntry)) {
tapGroupCell = touchGroupCell;
tapMediaCell = touchMediaCell;
draggingCellTouchX = touchX;
draggingCellTouchY = touchY;
draggingCell = null;
final long wasTapTime = tapTime = SystemClock.elapsedRealtime();
final PreviewGroupCell.MediaCell wasTapMediaCell = tapMediaCell;
AndroidUtilities.runOnUIThread(() -> {
if (listView.scrollingByUser || tapTime != wasTapTime || tapMediaCell != wasTapMediaCell) {
return;
}
startDragging(tapMediaCell);
RectF draggingCellRect = draggingCell.rect();
RectF draggingCellDrawingRect = draggingCell.drawingRect();
draggingCellLeft = (.5f + (draggingCellTouchX - draggingCellRect.left) / (float) draggingCellRect.width()) / 2;
draggingCellTop = (draggingCellTouchY - draggingCellRect.top) / (float) draggingCellRect.height();
draggingCellFromWidth = draggingCellDrawingRect.width();
draggingCellFromHeight = draggingCellDrawingRect.height();
try {
ChatAttachAlertPhotoLayoutPreview.this.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
} catch (Exception ignore) {}
}, ViewConfiguration.getLongPressTimeout());
invalidate();
result = true;
} else if (action == MotionEvent.ACTION_MOVE && draggingCell != null && !draggingCellHiding) {
draggingCellTouchX = touchX;
draggingCellTouchY = touchY;
if (!scrollerStarted) {
scrollerStarted = true;
postDelayed(scroller, 16);
}
invalidate();
result = true;
} else if (action == MotionEvent.ACTION_UP && draggingCell != null) {
PreviewGroupCell replaceGroupCell = null;
PreviewGroupCell.MediaCell replaceMediaCell = null;
if (touchGroupCell != null && touchMediaCell != null && touchMediaCell != draggingCell) {
replaceGroupCell = touchGroupCell;
replaceMediaCell = touchMediaCell;
} else if (draggingOverGroupCell != null && draggingOverMediaCell != null && draggingOverMediaCell != draggingCell && draggingOverMediaCell.photoEntry != draggingCell.photoEntry) {
replaceGroupCell = draggingOverGroupCell;
replaceMediaCell = draggingOverMediaCell;
}
if (replaceGroupCell != null && replaceMediaCell != null && replaceMediaCell != draggingCell) {
int draggingIndex = draggingCell.groupCell.group.photos.indexOf(draggingCell.photoEntry);
int tapIndex = replaceGroupCell.group.photos.indexOf(replaceMediaCell.photoEntry);
if (draggingIndex >= 0) {
draggingCell.groupCell.group.photos.remove(draggingIndex);
draggingCell.groupCell.setGroup(draggingCell.groupCell.group, true);
}
if (tapIndex >= 0) {
if (groupCells.indexOf(replaceGroupCell) > groupCells.indexOf(draggingCell.groupCell)) {
tapIndex++;
}
pushToGroup(replaceGroupCell, draggingCell.photoEntry, tapIndex);
if (draggingCell.groupCell != replaceGroupCell) {
PreviewGroupCell.MediaCell newDraggingCell = null;
final int mediaCount = replaceGroupCell.media.size();
for (int i = 0; i < mediaCount; ++i) {
PreviewGroupCell.MediaCell mediaCell = replaceGroupCell.media.get(i);
if (mediaCell.photoEntry == draggingCell.photoEntry) {
newDraggingCell = mediaCell;
break;
}
}
if (newDraggingCell != null) {
remeasure();
newDraggingCell.layoutFrom(draggingCell);
draggingCell = newDraggingCell;
newDraggingCell.groupCell = replaceGroupCell;
newDraggingCell.scale = draggingCell.fromScale = 1f;
remeasure();
}
}
}
try {
ChatAttachAlertPhotoLayoutPreview.this.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_RELEASE, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
} catch (Exception ignore) {}
updateGroups();
toPhotoLayout(photoLayout, false);
}
stopDragging();
result = true;
} else if (action == MotionEvent.ACTION_UP && draggingCell == null && tapMediaCell != null && tapGroupCell != null) {
RectF cellRect = tapMediaCell.drawingRect();
AndroidUtilities.rectTmp.set(cellRect.right - AndroidUtilities.dp(36.4f), tapGroupCell.top + cellRect.top, cellRect.right, tapGroupCell.top + cellRect.top + AndroidUtilities.dp(36.4f));
boolean tappedAtIndex = AndroidUtilities.rectTmp.contains(touchX, touchY - tapMediaCell.groupCell.y);
if (tappedAtIndex) {
if (getSelectedItemsCount() > 1) {
// short tap -> remove photo
final MediaController.PhotoEntry photo = tapMediaCell.photoEntry;
final int index = tapGroupCell.group.photos.indexOf(photo);
if (index >= 0) {
saveDeletedImageId(photo);
final PreviewGroupCell groupCell = tapGroupCell;
groupCell.group.photos.remove(index);
groupCell.setGroup(groupCell.group, true);
updateGroups();
toPhotoLayout(photoLayout, false);
final int currentUndoViewId = ++undoViewId;
undoView.showWithAction(0, ACTION_PREVIEW_MEDIA_DESELECTED, photo, null, () -> {
if (draggingAnimator != null) {
draggingAnimator.cancel();
}
draggingCell = null;
draggingT = 0;
pushToGroup(groupCell, photo, index);
updateGroups();
toPhotoLayout(photoLayout, false);
});
postDelayed(() -> {
if (currentUndoViewId == undoViewId && undoView.isShown()) {
undoView.hide(true, 1);
}
}, 1000 * 4);
}
if (draggingAnimator != null) {
draggingAnimator.cancel();
}
}
} else {
calcPhotoArrays();
ArrayList<MediaController.PhotoEntry> arrayList = getPhotos();
int position = arrayList.indexOf(tapMediaCell.photoEntry);
ChatActivity chatActivity;
int type;
if (parentAlert.avatarPicker != 0) {
chatActivity = null;
type = PhotoViewer.SELECT_TYPE_AVATAR;
} else if (parentAlert.baseFragment instanceof ChatActivity) {
chatActivity = (ChatActivity) parentAlert.baseFragment;
type = 0;
} else {
chatActivity = null;
type = 4;
}
if (!parentAlert.delegate.needEnterComment()) {
AndroidUtilities.hideKeyboard(parentAlert.baseFragment.getFragmentView().findFocus());
AndroidUtilities.hideKeyboard(parentAlert.getContainer().findFocus());
}
PhotoViewer.getInstance().setParentActivity(parentAlert.baseFragment.getParentActivity(), resourcesProvider);
PhotoViewer.getInstance().setParentAlert(parentAlert);
PhotoViewer.getInstance().setMaxSelectedPhotos(parentAlert.maxSelectedPhotos, parentAlert.allowOrder);
photoViewerProvider.init(arrayList);
ArrayList<Object> objectArrayList = new ArrayList<>(arrayList);
PhotoViewer.getInstance().openPhotoForSelect(objectArrayList, position, type, false, photoViewerProvider, chatActivity);
if (photoLayout.captionForAllMedia()) {
PhotoViewer.getInstance().setCaption(parentAlert.getCommentTextView().getText());
}
}
tapMediaCell = null;
tapTime = 0;
draggingCell = null;
draggingT = 0;
result = true;
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
tapTime = 0;
removeCallbacks(scroller);
scrollerStarted = false;
if (!result) {
stopDragging();
result = true;
}
}
return result;
}
private void pushToGroup(PreviewGroupCell groupCell, MediaController.PhotoEntry photoEntry, int index) {
groupCell.group.photos.add(Math.min(groupCell.group.photos.size(), index), photoEntry);
if (groupCell.group.photos.size() == 11) {
MediaController.PhotoEntry jumpPhoto = groupCell.group.photos.get(10);
groupCell.group.photos.remove(10);
int groupIndex = groupCells.indexOf(groupCell);
if (groupIndex >= 0) {
PreviewGroupCell nextGroupCell = groupIndex + 1 == groupCells.size() ? null : groupCells.get(groupIndex + 1);
if (nextGroupCell == null) {
nextGroupCell = new PreviewGroupCell();
ArrayList<MediaController.PhotoEntry> newPhotos = new ArrayList<>();
newPhotos.add(jumpPhoto);
nextGroupCell.setGroup(new GroupCalculator(newPhotos), true);
invalidate();
} else {
pushToGroup(nextGroupCell, jumpPhoto, 0);
}
}
}
groupCell.setGroup(groupCell.group, true);
}
private void updateGroups() {
final int groupCellsCount = groupCells.size();
for (int i = 0; i < groupCellsCount; ++i) {
PreviewGroupCell groupCell = groupCells.get(i);
if (groupCell.group.photos.size() < 10 && i < groupCells.size() - 1) {
int photosToTake = 10 - groupCell.group.photos.size();
PreviewGroupCell nextGroup = groupCells.get(i + 1);
ArrayList<MediaController.PhotoEntry> takenPhotos = new ArrayList<>();
photosToTake = Math.min(photosToTake, nextGroup.group.photos.size());
for (int j = 0; j < photosToTake; ++j) {
takenPhotos.add(nextGroup.group.photos.remove(0));
}
groupCell.group.photos.addAll(takenPhotos);
groupCell.setGroup(groupCell.group, true);
nextGroup.setGroup(nextGroup.group, true);
}
}
}
private HashMap<MediaController.PhotoEntry, ImageReceiver> images = new HashMap<>();
private class PreviewGroupCell {
public float y = 0;
public int indexStart = 0;
private final long updateDuration = 200 * durationMultiplier;
private long lastMediaUpdate = 0;
private float groupWidth = 0, groupHeight = 0;
private float previousGroupWidth = 0, previousGroupHeight = 0;
public ArrayList<MediaCell> media = new ArrayList<>();
private class MediaCell {
public PreviewGroupCell groupCell = PreviewGroupCell.this;
public MediaController.PhotoEntry photoEntry;
public ImageReceiver image;
private RectF fromRect = null;
public RectF rect = new RectF();
private long lastUpdate = 0;
private final long updateDuration = 200 * durationMultiplier;
private int positionFlags = 0;
public float fromScale = 1f;
public float scale = 0f;
public RectF fromRoundRadiuses = null;
public RectF roundRadiuses = new RectF();
private String videoDurationText = null;
private void setImage(MediaController.PhotoEntry photoEntry) {
this.photoEntry = photoEntry;
if (photoEntry != null && photoEntry.isVideo) {
videoDurationText = AndroidUtilities.formatShortDuration(photoEntry.duration);
} else {
videoDurationText = null;
}
if (image == null) {
image = new ImageReceiver(PreviewGroupsView.this);
}
if (photoEntry != null) {
if (photoEntry.thumbPath != null) {
image.setImage(ImageLocation.getForPath(photoEntry.thumbPath), null, null, null, Theme.chat_attachEmptyDrawable, 0, null, null, 0);
} else if (photoEntry.path != null) {
if (photoEntry.isVideo) {
image.setImage(ImageLocation.getForPath("vthumb://" + photoEntry.imageId + ":" + photoEntry.path), null, null, null, Theme.chat_attachEmptyDrawable, 0, null, null, 0);
image.setAllowStartAnimation(true);
} else {
image.setOrientation(photoEntry.orientation, true);
image.setImage(ImageLocation.getForPath("thumb://" + photoEntry.imageId + ":" + photoEntry.path), null, null, null, Theme.chat_attachEmptyDrawable, 0, null, null, 0);
}
} else {
image.setImageBitmap(Theme.chat_attachEmptyDrawable);
}
}
}
private void layoutFrom(MediaCell fromCell) {
this.fromScale = AndroidUtilities.lerp(fromCell.fromScale, fromCell.scale, fromCell.getT());
if (this.fromRect == null) {
this.fromRect = new RectF();
}
RectF myRect = new RectF();
if (this.fromRect == null) {
myRect.set(rect);
} else {
AndroidUtilities.lerp(fromRect, rect, getT(), myRect);
}
if (fromCell.fromRect != null) {
AndroidUtilities.lerp(fromCell.fromRect, fromCell.rect, fromCell.getT(), this.fromRect);
this.fromRect.set(
myRect.centerX() - this.fromRect.width() / 2 * fromCell.groupCell.width / width,
myRect.centerY() - this.fromRect.height() / 2 * fromCell.groupCell.height / height,
myRect.centerX() + this.fromRect.width() / 2 * fromCell.groupCell.width / width,
myRect.centerY() + this.fromRect.height() / 2 * fromCell.groupCell.height / height
);
} else {
this.fromRect.set(
myRect.centerX() - fromCell.rect.width() / 2 * fromCell.groupCell.width / width,
myRect.centerY() - fromCell.rect.height() / 2 * fromCell.groupCell.height / height,
myRect.centerX() + fromCell.rect.width() / 2 * fromCell.groupCell.width / width,
myRect.centerY() + fromCell.rect.height() / 2 * fromCell.groupCell.height / height
);
}
fromScale = AndroidUtilities.lerp(fromScale, scale, getT());
this.lastUpdate = SystemClock.elapsedRealtime();
}
private void layout(GroupCalculator group, MessageObject.GroupedMessagePosition pos, boolean animated) {
if (group == null || pos == null) {
if (animated) {
final long now = SystemClock.elapsedRealtime();
fromScale = AndroidUtilities.lerp(fromScale, scale, getT());
if (fromRect != null) {
AndroidUtilities.lerp(fromRect, rect, getT(), fromRect);
}
scale = 0f;
lastUpdate = now;
} else {
scale = fromScale = 0f;
}
return;
}
positionFlags = pos.flags;
if (animated) {
final float t = getT();
if (fromRect != null) {
AndroidUtilities.lerp(fromRect, rect, t, fromRect);
}
if (fromRoundRadiuses != null) {
AndroidUtilities.lerp(fromRoundRadiuses, roundRadiuses, t, fromRoundRadiuses);
}
fromScale = AndroidUtilities.lerp(fromScale, scale, t);
lastUpdate = SystemClock.elapsedRealtime();
}
float x = pos.left / group.width,
y = pos.top / group.height,
w = pos.pw / (float) group.width,
h = pos.ph / group.height;
scale = 1f;
rect.set(x, y, x + w, y + h);
final float r = AndroidUtilities.dp(2),
R = AndroidUtilities.dp(SharedConfig.bubbleRadius - 1);
roundRadiuses.set(
(positionFlags & (POSITION_FLAG_TOP | POSITION_FLAG_LEFT)) == (POSITION_FLAG_TOP | POSITION_FLAG_LEFT) ? R : r,
(positionFlags & (POSITION_FLAG_TOP | POSITION_FLAG_RIGHT)) == (POSITION_FLAG_TOP | POSITION_FLAG_RIGHT) ? R : r,
(positionFlags & (POSITION_FLAG_BOTTOM | POSITION_FLAG_RIGHT)) == (POSITION_FLAG_BOTTOM | POSITION_FLAG_RIGHT) ? R : r,
(positionFlags & (POSITION_FLAG_BOTTOM | POSITION_FLAG_LEFT)) == (POSITION_FLAG_BOTTOM | POSITION_FLAG_LEFT) ? R : r
);
if (fromRect == null) {
fromRect = new RectF();
fromRect.set(rect);
}
if (fromRoundRadiuses == null) {
fromRoundRadiuses = new RectF();
fromRoundRadiuses.set(roundRadiuses);
}
}
public float getT() {
return interpolator.getInterpolation(Math.min(1, (SystemClock.elapsedRealtime() - lastUpdate) / (float) updateDuration));
}
@NonNull
@Override
protected MediaCell clone() {
MediaCell newMediaCell = new MediaCell();
newMediaCell.rect.set(this.rect);
newMediaCell.image = this.image;
newMediaCell.photoEntry = this.photoEntry;
return newMediaCell;
}
private RectF tempRect = new RectF();
public RectF rect() {
return rect(getT());
}
public RectF rect(float t) {
if (rect == null || image == null) {
tempRect.set(0, 0, 0, 0);
return tempRect;
}
float x = left + rect.left * width,
y = top + rect.top * height,
w = rect.width() * width,
h = rect.height() * height;
if (t < 1f && fromRect != null) {
x = AndroidUtilities.lerp(left + fromRect.left * width, x, t);
y = AndroidUtilities.lerp(top + fromRect.top * height, y, t);
w = AndroidUtilities.lerp(fromRect.width() * width, w, t);
h = AndroidUtilities.lerp(fromRect.height() * height, h, t);
}
if ((positionFlags & POSITION_FLAG_TOP) == 0) {
y += halfGap;
h -= halfGap;
}
if ((positionFlags & POSITION_FLAG_BOTTOM) == 0) {
h -= halfGap;
}
if ((positionFlags & POSITION_FLAG_LEFT) == 0) {
x += halfGap;
w -= halfGap;
}
if ((positionFlags & POSITION_FLAG_RIGHT) == 0) {
w -= halfGap;
}
tempRect.set(x, y, x + w, y + h);
return tempRect;
}
public RectF drawingRect() {
if (rect == null || image == null) {
tempRect.set(0, 0, 0, 0);
return tempRect;
}
final float dragging = draggingCell != null && draggingCell.photoEntry == photoEntry ? draggingT : 0,
scale = AndroidUtilities.lerp(this.fromScale, this.scale, getT()) * (.8f + .2f * (1f - dragging));
RectF myRect = this.rect();
myRect.set(
myRect.left + myRect.width() * (1f - scale) / 2f,
myRect.top + myRect.height() * (1f - scale) / 2f,
myRect.left + myRect.width() * (1f + scale) / 2f,
myRect.top + myRect.height() * (1f + scale) / 2f
);
return myRect;
}
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private TextPaint textPaint;
private TextPaint videoDurationTextPaint;
private Paint bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Bitmap indexBitmap = null;
private String indexBitmapText = null;
private Bitmap videoDurationBitmap = null;
private String videoDurationBitmapText = null;
private Rect indexIn = new Rect(), indexOut = new Rect();
private Rect durationIn = new Rect(), durationOut = new Rect();
private void drawPhotoIndex(Canvas canvas, float top, float right, String indexText, float scale, float alpha) {
final int radius = AndroidUtilities.dp(12),
strokeWidth = AndroidUtilities.dp(1.2f),
sz = (radius + strokeWidth) * 2,
pad = strokeWidth * 4;
if (indexText != null && (indexBitmap == null || indexBitmapText == null || !indexBitmapText.equals(indexText))) {
if (indexBitmap == null) {
indexBitmap = Bitmap.createBitmap(sz, sz, Bitmap.Config.ARGB_8888);
}
Canvas bitmapCanvas = new Canvas(indexBitmap);
bitmapCanvas.drawColor(0x00000000);
if (textPaint == null) {
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
}
textPaint.setColor(getThemedColor(Theme.key_chat_attachCheckBoxCheck));
final float textSize;
switch (indexText.length()) {
case 0:
case 1:
case 2:
textSize = 14f;
break;
case 3:
textSize = 10f;
break;
default:
textSize = 8f;
}
textPaint.setTextSize(AndroidUtilities.dp(textSize));
float cx = sz / 2f, cy = sz / 2f;
paint.setColor(getThemedColor(Theme.key_chat_attachCheckBoxBackground));
bitmapCanvas.drawCircle((int) cx, (int) cy, radius, paint);
strokePaint.setColor(AndroidUtilities.getOffsetColor(0xffffffff, getThemedColor(Theme.key_chat_attachCheckBoxCheck), 1f, 1f));
strokePaint.setStyle(Paint.Style.STROKE);
strokePaint.setStrokeWidth(strokeWidth);
bitmapCanvas.drawCircle((int) cx, (int) cy, radius, strokePaint);
bitmapCanvas.drawText(indexText, cx - textPaint.measureText(indexText) / 2f, cy + AndroidUtilities.dp(1) + AndroidUtilities.dp(textSize / 4f), textPaint);
indexIn.set(0, 0, sz, sz);
indexBitmapText = indexText;
}
if (indexBitmap != null) {
indexOut.set((int) (right - sz * scale + pad), (int) (top - pad), (int) (right + pad), (int) (top - pad + sz * scale));
bitmapPaint.setAlpha((int) (255 * alpha));
canvas.drawBitmap(indexBitmap, indexIn, indexOut, bitmapPaint);
}
}
private void drawDuration(Canvas canvas, float left, float bottom, String durationText, float scale, float alpha) {
if (durationText != null) {
if (videoDurationBitmap == null || videoDurationBitmapText == null || !videoDurationBitmapText.equals(durationText)) {
if (videoDurationTextPaint == null) {
videoDurationTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
videoDurationTextPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
videoDurationTextPaint.setColor(0xffffffff);
}
final float textSize = AndroidUtilities.dp(12);
videoDurationTextPaint.setTextSize(textSize);
float textWidth = videoDurationTextPaint.measureText(durationText);
float width = videoPlayImage.getIntrinsicWidth() + textWidth + AndroidUtilities.dp(15),
height = Math.max(textSize, videoPlayImage.getIntrinsicHeight() + AndroidUtilities.dp(4));
int w = (int) Math.ceil(width), h = (int) Math.ceil(height);
if (videoDurationBitmap == null || videoDurationBitmap.getWidth() != w || videoDurationBitmap.getHeight() != h) {
if (videoDurationBitmap != null) {
videoDurationBitmap.recycle();
}
videoDurationBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
}
Canvas bitmapCanvas = new Canvas(videoDurationBitmap);
AndroidUtilities.rectTmp.set(0, 0, width, height);
bitmapCanvas.drawRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(4), AndroidUtilities.dp(4), Theme.chat_timeBackgroundPaint);
int imageLeft = (int) AndroidUtilities.dp(5),
imageTop = (int) ((height - videoPlayImage.getIntrinsicHeight()) / 2);
videoPlayImage.setBounds(imageLeft, imageTop, imageLeft + videoPlayImage.getIntrinsicWidth(), imageTop + videoPlayImage.getIntrinsicHeight());
videoPlayImage.draw(bitmapCanvas);
bitmapCanvas.drawText(durationText, AndroidUtilities.dp(18), textSize + AndroidUtilities.dp(-0.7f), videoDurationTextPaint);
durationIn.set(0, 0, w, h);
videoDurationBitmapText = durationText;
}
int w = videoDurationBitmap.getWidth(), h = videoDurationBitmap.getHeight();
durationOut.set((int) left, (int) (bottom - h * scale), (int) (left + w * scale), (int) bottom);
bitmapPaint.setAlpha((int) (255 * alpha));
canvas.drawBitmap(videoDurationBitmap, durationIn, durationOut, bitmapPaint);
}
}
private float visibleT = 1;
private long lastVisibleTUpdate = 0;
public boolean draw(Canvas canvas) {
return draw(canvas, false);
}
public boolean draw(Canvas canvas, boolean ignoreBounds) {
return draw(canvas, getT(), ignoreBounds);
}
public boolean draw(Canvas canvas, float t, boolean ignoreBounds) {
if (rect == null || image == null) {
return false;
}
final float dragging = draggingCell == this ? draggingT : 0;
float scale = AndroidUtilities.lerp(this.fromScale, this.scale, t);
if (scale <= 0f) {
return false;
}
RectF drawingRect = this.drawingRect();
float R = AndroidUtilities.dp(SharedConfig.bubbleRadius - 1);
float tl = roundRadiuses.left, tr = roundRadiuses.top, br = roundRadiuses.right, bl = roundRadiuses.bottom;
if (t < 1f && fromRoundRadiuses != null) {
tl = AndroidUtilities.lerp(fromRoundRadiuses.left, tl, t);
tr = AndroidUtilities.lerp(fromRoundRadiuses.top, tr, t);
br = AndroidUtilities.lerp(fromRoundRadiuses.right, br, t);
bl = AndroidUtilities.lerp(fromRoundRadiuses.bottom, bl, t);
}
tl = AndroidUtilities.lerp(tl, R, dragging);
tr = AndroidUtilities.lerp(tr, R, dragging);
br = AndroidUtilities.lerp(br, R, dragging);
bl = AndroidUtilities.lerp(bl, R, dragging);
if (ignoreBounds) {
canvas.save();
canvas.translate(-drawingRect.centerX(), -drawingRect.centerY());
}
image.setRoundRadius((int) tl, (int) tr, (int) br, (int) bl);
image.setImageCoords(drawingRect.left, drawingRect.top, drawingRect.width(), drawingRect.height());
image.setAlpha(scale);
image.draw(canvas);
int index = indexStart + group.photos.indexOf(photoEntry);
String indexText = index >= 0 ? (index + 1) + "" : null;
float shouldVisibleT = image.getVisible() ? 1 : 0;
boolean needVisibleTUpdate;
if (needVisibleTUpdate = Math.abs(visibleT - shouldVisibleT) > 0.01f) {
long tx = Math.min(17, SystemClock.elapsedRealtime() - lastVisibleTUpdate);
lastVisibleTUpdate = SystemClock.elapsedRealtime();
float upd = tx / 100f;
if (shouldVisibleT < visibleT) {
visibleT = Math.max(0, visibleT - upd);
} else {
visibleT = Math.min(1, visibleT + upd);
}
}
drawPhotoIndex(canvas, drawingRect.top + AndroidUtilities.dp(10), drawingRect.right - AndroidUtilities.dp(10), indexText, scale, scale * visibleT);
drawDuration(canvas, drawingRect.left + AndroidUtilities.dp(4), drawingRect.bottom - AndroidUtilities.dp(4), videoDurationText, scale, scale * visibleT);
if (ignoreBounds) {
canvas.restore();
}
return t < 1f || needVisibleTUpdate;
}
}
private Interpolator interpolator = CubicBezierInterpolator.EASE_BOTH;
private GroupCalculator group;
private void setGroup(GroupCalculator group, boolean animated) {
this.group = group;
if (group == null) {
return;
}
group.calculate();
final long now = SystemClock.elapsedRealtime();
if (now - lastMediaUpdate < updateDuration) {
final float t = (now - lastMediaUpdate) / (float) updateDuration;
previousGroupHeight = AndroidUtilities.lerp(previousGroupHeight, groupHeight, t);
previousGroupWidth = AndroidUtilities.lerp(previousGroupWidth, groupWidth, t);
} else {
previousGroupHeight = groupHeight;
previousGroupWidth = groupWidth;
}
groupWidth = group.width / 1000f;
groupHeight = group.height;
lastMediaUpdate = animated ? now : 0;
List<MediaController.PhotoEntry> photoEntries = new ArrayList<>(group.positions.keySet());
final int photoEntriesCount = photoEntries.size();
for (int j = 0; j < photoEntriesCount; ++j) {
MediaController.PhotoEntry photoEntry = photoEntries.get(j);
MessageObject.GroupedMessagePosition pos = group.positions.get(photoEntry);
MediaCell properCell = null;
final int mediaCount = media.size();
for (int i = 0; i < mediaCount; ++i) {
MediaCell cell = media.get(i);
if (cell.photoEntry == photoEntry) {
properCell = cell;
break;
}
}
if (properCell == null) {
// new cell
properCell = new MediaCell();
properCell.setImage(photoEntry);
properCell.layout(group, pos, animated);
media.add(properCell);
} else {
properCell.layout(group, pos, animated);
}
}
int mediaCount = media.size();
for (int i = 0; i < mediaCount; ++i) {
MediaCell cell = media.get(i);
if (!group.positions.containsKey(cell.photoEntry)) {
// old cell, remove it
if (cell.scale <= 0 && cell.lastUpdate + cell.updateDuration <= now) {
media.remove(i);
i--;
mediaCount--;
} else {
cell.layout(null, null, animated);
}
}
}
PreviewGroupsView.this.invalidate();
}
final int padding = AndroidUtilities.dp(4);
final int gap = AndroidUtilities.dp(2),
halfGap = gap / 2;
private float left, right, top, bottom, width, height;
public float getT() {
return interpolator.getInterpolation(Math.min(1, (SystemClock.elapsedRealtime() - lastMediaUpdate) / (float) updateDuration));
}
public float measure() {
final float maxHeight = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f;
return AndroidUtilities.lerp(previousGroupHeight, this.groupHeight, getT()) * maxHeight * getPreviewScale(); // height
}
public float maxHeight() {
final float maxHeight = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f;
return getT() >= 0.95f ? this.groupHeight * maxHeight * getPreviewScale() : measure();
}
public boolean needToUpdate = false;
public void invalidate() {
needToUpdate = true;
}
private Theme.MessageDrawable messageBackground = (Theme.MessageDrawable) getThemedDrawable(Theme.key_drawable_msgOutMedia);
private Theme.MessageDrawable.PathDrawParams backgroundCacheParams = new Theme.MessageDrawable.PathDrawParams();
public boolean draw(Canvas canvas) {
boolean update = false;
final float t = interpolator.getInterpolation(Math.min(1, (SystemClock.elapsedRealtime() - lastMediaUpdate) / (float) updateDuration));
if (t < 1f) {
update = true;
}
final float maxHeight = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f;
float groupWidth = AndroidUtilities.lerp(previousGroupWidth, this.groupWidth, t) * getWidth() * getPreviewScale(),
groupHeight = AndroidUtilities.lerp(previousGroupHeight, this.groupHeight, t) * maxHeight * getPreviewScale();
if (messageBackground != null) {
top = 0;
left = (getWidth() - Math.max(padding, groupWidth)) / 2f;
right = (getWidth() + Math.max(padding, groupWidth)) / 2f;
bottom = Math.max(padding * 2, groupHeight);
messageBackground.setTop((int) 0, (int) groupWidth, (int) groupHeight, 0, 0, 0, false, false);
messageBackground.setBounds((int) left, (int) top, (int) right, (int) bottom);
float alpha = 1f;
if (this.groupWidth <= 0) {
alpha = 1f - t;
} else if (this.previousGroupWidth <= 0) {
alpha = t;
}
messageBackground.setAlpha((int) (255 * alpha));
messageBackground.drawCached(canvas, backgroundCacheParams);
top += padding;
left += padding;
bottom -= padding;
right -= padding;
}
width = right - left;
height = bottom - top;
final int count = media.size();
for (int i = 0; i < count; ++i) {
MediaCell cell = media.get(i);
if (cell == null) {
continue;
}
if (draggingCell != null && draggingCell.photoEntry == cell.photoEntry) {
continue;
}
if (cell.draw(canvas)) {
update = true;
}
}
return update;
}
}
}
public Drawable getThemedDrawable(String drawableKey) {
Drawable drawable = themeDelegate.getDrawable(drawableKey);
return drawable != null ? drawable : Theme.getThemeDrawable(drawableKey);
}
}