/*
* Copyright (C) 2011 Whisper Systems
*
* 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 .
*/
package org.tm.archive.conversation;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.text.Annotation;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.BackgroundColorSpan;
import android.text.style.CharacterStyle;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.ColorInt;
import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import androidx.media3.common.MediaItem;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.RequestManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.common.collect.Sets;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.DimensionUnit;
import org.signal.core.util.StringUtil;
import org.signal.core.util.logging.Log;
import org.signal.ringrtc.CallLinkRootKey;
import org.tm.archive.BindableConversationItem;
import org.tm.archive.R;
import org.tm.archive.attachments.Attachment;
import org.tm.archive.attachments.AttachmentId;
import org.tm.archive.attachments.DatabaseAttachment;
import org.tm.archive.badges.BadgeImageView;
import org.tm.archive.badges.gifts.GiftMessageView;
import org.tm.archive.badges.gifts.OpenableGift;
import org.tm.archive.calls.links.CallLinkJoinButton;
import org.tm.archive.calls.links.CallLinks;
import org.tm.archive.components.AlertView;
import org.tm.archive.components.AudioView;
import org.tm.archive.components.AvatarImageView;
import org.tm.archive.components.BorderlessImageView;
import org.tm.archive.components.ConversationItemFooter;
import org.tm.archive.components.ConversationItemThumbnail;
import org.tm.archive.components.DocumentView;
import org.tm.archive.components.LinkPreviewView;
import org.tm.archive.components.Outliner;
import org.tm.archive.components.PlaybackSpeedToggleTextView;
import org.tm.archive.components.QuoteView;
import org.tm.archive.components.SharedContactView;
import org.tm.archive.components.ThumbnailView;
import org.tm.archive.components.emoji.EmojiTextView;
import org.tm.archive.components.mention.MentionAnnotation;
import org.tm.archive.contactshare.Contact;
import org.tm.archive.conversation.clicklisteners.AttachmentCancelClickListener;
import org.tm.archive.conversation.clicklisteners.ResendClickListener;
import org.tm.archive.conversation.colors.Colorizer;
import org.tm.archive.conversation.mutiselect.MultiselectCollection;
import org.tm.archive.conversation.mutiselect.MultiselectPart;
import org.tm.archive.conversation.ui.payment.PaymentMessageView;
import org.tm.archive.conversation.v2.items.InteractiveConversationElement;
import org.tm.archive.conversation.v2.items.V2ConversationItemUtils;
import org.tm.archive.database.AttachmentTable;
import org.tm.archive.database.MediaTable;
import org.tm.archive.database.model.MessageRecord;
import org.tm.archive.database.model.MmsMessageRecord;
import org.tm.archive.database.model.Quote;
import org.tm.archive.dependencies.ApplicationDependencies;
import org.tm.archive.events.PartProgressEvent;
import org.tm.archive.giph.mp4.GiphyMp4PlaybackPolicy;
import org.tm.archive.giph.mp4.GiphyMp4PlaybackPolicyEnforcer;
import org.tm.archive.jobmanager.JobManager;
import org.tm.archive.jobs.AttachmentDownloadJob;
import org.tm.archive.keyvalue.SignalStore;
import org.tm.archive.linkpreview.LinkPreview;
import org.tm.archive.mediapreview.MediaIntentFactory;
import org.tm.archive.mediapreview.MediaPreviewCache;
import org.tm.archive.mediapreview.MediaPreviewV2Fragment;
import org.tm.archive.mms.ImageSlide;
import org.tm.archive.mms.PartAuthority;
import org.tm.archive.mms.Slide;
import org.tm.archive.mms.SlideClickListener;
import org.tm.archive.mms.SlideDeck;
import org.tm.archive.mms.SlidesClickedListener;
import org.tm.archive.mms.TextSlide;
import org.tm.archive.mms.VideoSlide;
import org.tm.archive.reactions.ReactionsConversationView;
import org.tm.archive.recipients.LiveRecipient;
import org.tm.archive.recipients.Recipient;
import org.tm.archive.recipients.RecipientForeverObserver;
import org.tm.archive.recipients.RecipientId;
import org.tm.archive.revealable.ViewOnceMessageView;
import org.tm.archive.util.DateUtils;
import org.tm.archive.util.FeatureFlags;
import org.tm.archive.util.InterceptableLongClickCopyLinkSpan;
import org.tm.archive.util.LongClickMovementMethod;
import org.tm.archive.util.MediaUtil;
import org.tm.archive.util.MessageRecordUtil;
import org.tm.archive.util.PlaceholderURLSpan;
import org.tm.archive.util.Projection;
import org.tm.archive.util.ProjectionList;
import org.tm.archive.util.SearchUtil;
import org.tm.archive.util.ThemeUtil;
import org.tm.archive.util.UrlClickHandler;
import org.tm.archive.util.Util;
import org.tm.archive.util.VibrateUtil;
import org.tm.archive.util.ViewUtil;
import org.tm.archive.util.views.NullableStub;
import org.tm.archive.util.views.Stub;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
/**
* A view that displays an individual conversation item within a conversation
* thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter.
*
* @author Moxie Marlinspike
*/
public final class ConversationItem extends RelativeLayout implements BindableConversationItem,
RecipientForeverObserver,
OpenableGift,
InteractiveConversationElement
{
private static final String TAG = Log.tag(ConversationItem.class);
private static final int MAX_MEASURE_CALLS = 3;
private static final Rect SWIPE_RECT = new Rect();
public static final float LONG_PRESS_SCALE_FACTOR = 0.95f;
private static final int SHRINK_BUBBLE_DELAY_MILLIS = 100;
private static final long MAX_CLUSTERING_TIME_DIFF = TimeUnit.MINUTES.toMillis(3);
private static final int CONDENSED_MODE_MAX_LINES = 3;
private static final SearchUtil.StyleFactory STYLE_FACTORY = () -> new CharacterStyle[] { new BackgroundColorSpan(Color.YELLOW), new ForegroundColorSpan(Color.BLACK) };
private ConversationMessage conversationMessage;
private MessageRecord messageRecord;
private Optional nextMessageRecord;
private Locale locale;
private boolean groupThread;
private LiveRecipient author;
private RequestManager requestManager;
private Optional previousMessage;
private ConversationItemDisplayMode displayMode;
private ConversationItemBodyBubble bodyBubble;
private View reply;
private View replyIcon;
@Nullable private ViewGroup contactPhotoHolder;
@Nullable private QuoteView quoteView;
private EmojiTextView bodyText;
private ConversationItemFooter footer;
@Nullable private ConversationItemFooter stickerFooter;
@Nullable private TextView groupSender;
@Nullable private View groupSenderHolder;
private AvatarImageView contactPhoto;
private AlertView alertView;
private ReactionsConversationView reactionsView;
private BadgeImageView badgeImageView;
private View storyReactionLabelWrapper;
private TextView storyReactionLabel;
private View quotedIndicator;
private View scheduledIndicator;
private @NonNull Set batchSelected = new HashSet<>();
private final @NonNull Outliner outliner = new Outliner();
private final @NonNull Outliner pulseOutliner = new Outliner();
private final @NonNull List outliners = new ArrayList<>(2);
private LiveRecipient conversationRecipient;
private NullableStub mediaThumbnailStub;
private Stub audioViewStub;
private Stub documentViewStub;
private Stub sharedContactStub;
private Stub linkPreviewStub;
private Stub stickerStub;
private Stub revealableStub;
private Stub joinCallLinkStub;
private Stub