mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-25 19:29:54 +01:00
Add support for inline video playback of gifs in Conversation.
This commit is contained in:
committed by
Greyson Parrelli
parent
32d79ead15
commit
281630e751
@@ -112,6 +112,7 @@ import org.thoughtcrime.securesms.components.HidingLinearLayout;
|
||||
import org.thoughtcrime.securesms.components.InputAwareLayout;
|
||||
import org.thoughtcrime.securesms.components.InputPanel;
|
||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
|
||||
import org.thoughtcrime.securesms.components.MaskView;
|
||||
import org.thoughtcrime.securesms.components.SendButton;
|
||||
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||
@@ -3420,7 +3421,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleReaction(@NonNull View maskTarget,
|
||||
public void handleReaction(@NonNull MaskView.MaskTarget maskTarget,
|
||||
@NonNull MessageRecord messageRecord,
|
||||
@NonNull Toolbar.OnMenuItemClickListener toolbarListener,
|
||||
@NonNull ConversationReactionOverlay.OnHideListener onHideListener)
|
||||
@@ -3451,7 +3452,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleReactionDetails(@NonNull View maskTarget) {
|
||||
public void handleReactionDetails(@NonNull MaskView.MaskTarget maskTarget) {
|
||||
reactionDelegate.showMask(maskTarget, titleView.getMeasuredHeight(), inputAreaHeight());
|
||||
}
|
||||
|
||||
|
||||
@@ -36,12 +36,18 @@ import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.paging.PagingController;
|
||||
import org.thoughtcrime.securesms.BindableConversationItem;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.MaskView;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Playable;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Projection;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
@@ -50,6 +56,7 @@ import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
@@ -100,11 +107,12 @@ public class ConversationAdapter
|
||||
private final Locale locale;
|
||||
private final Recipient recipient;
|
||||
|
||||
private final Set<ConversationMessage> selected;
|
||||
private final List<ConversationMessage> fastRecords;
|
||||
private final Set<Long> releasedFastRecords;
|
||||
private final Calendar calendar;
|
||||
private final MessageDigest digest;
|
||||
private final Set<ConversationMessage> selected;
|
||||
private final List<ConversationMessage> fastRecords;
|
||||
private final Set<Long> releasedFastRecords;
|
||||
private final Calendar calendar;
|
||||
private final MessageDigest digest;
|
||||
private final AttachmentMediaSourceFactory attachmentMediaSourceFactory;
|
||||
|
||||
private String searchQuery;
|
||||
private ConversationMessage recordToPulse;
|
||||
@@ -113,12 +121,14 @@ public class ConversationAdapter
|
||||
private PagingController pagingController;
|
||||
private boolean hasWallpaper;
|
||||
private boolean isMessageRequestAccepted;
|
||||
private ConversationMessage inlineContent;
|
||||
|
||||
ConversationAdapter(@NonNull LifecycleOwner lifecycleOwner,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@Nullable ItemClickListener clickListener,
|
||||
@NonNull Recipient recipient)
|
||||
@NonNull Recipient recipient,
|
||||
@NonNull AttachmentMediaSourceFactory attachmentMediaSourceFactory)
|
||||
{
|
||||
super(new DiffUtil.ItemCallback<ConversationMessage>() {
|
||||
@Override
|
||||
@@ -134,17 +144,18 @@ public class ConversationAdapter
|
||||
|
||||
this.lifecycleOwner = lifecycleOwner;
|
||||
|
||||
this.glideRequests = glideRequests;
|
||||
this.locale = locale;
|
||||
this.clickListener = clickListener;
|
||||
this.recipient = recipient;
|
||||
this.selected = new HashSet<>();
|
||||
this.fastRecords = new ArrayList<>();
|
||||
this.releasedFastRecords = new HashSet<>();
|
||||
this.calendar = Calendar.getInstance();
|
||||
this.digest = getMessageDigestOrThrow();
|
||||
this.hasWallpaper = recipient.hasWallpaper();
|
||||
this.isMessageRequestAccepted = true;
|
||||
this.glideRequests = glideRequests;
|
||||
this.locale = locale;
|
||||
this.clickListener = clickListener;
|
||||
this.recipient = recipient;
|
||||
this.selected = new HashSet<>();
|
||||
this.fastRecords = new ArrayList<>();
|
||||
this.releasedFastRecords = new HashSet<>();
|
||||
this.calendar = Calendar.getInstance();
|
||||
this.digest = getMessageDigestOrThrow();
|
||||
this.hasWallpaper = recipient.hasWallpaper();
|
||||
this.isMessageRequestAccepted = true;
|
||||
this.attachmentMediaSourceFactory = attachmentMediaSourceFactory;
|
||||
|
||||
setHasStableIds(true);
|
||||
}
|
||||
@@ -257,7 +268,9 @@ public class ConversationAdapter
|
||||
searchQuery,
|
||||
conversationMessage == recordToPulse,
|
||||
hasWallpaper,
|
||||
isMessageRequestAccepted);
|
||||
isMessageRequestAccepted,
|
||||
attachmentMediaSourceFactory,
|
||||
conversationMessage == inlineContent);
|
||||
|
||||
if (conversationMessage == recordToPulse) {
|
||||
recordToPulse = null;
|
||||
@@ -610,7 +623,14 @@ public class ConversationAdapter
|
||||
}
|
||||
}
|
||||
|
||||
static class ConversationViewHolder extends RecyclerView.ViewHolder {
|
||||
public void playInlineContent(@Nullable ConversationMessage conversationMessage) {
|
||||
if (this.inlineContent != conversationMessage) {
|
||||
this.inlineContent = conversationMessage;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
final static class ConversationViewHolder extends RecyclerView.ViewHolder implements GiphyMp4Playable {
|
||||
public ConversationViewHolder(final @NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
@@ -618,6 +638,36 @@ public class ConversationAdapter
|
||||
public BindableConversationItem getBindable() {
|
||||
return (BindableConversationItem) itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showProjectionArea() {
|
||||
getBindable().showProjectionArea();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideProjectionArea() {
|
||||
getBindable().hideProjectionArea();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable MediaSource getMediaSource() {
|
||||
return getBindable().getMediaSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GiphyMp4PlaybackPolicyEnforcer getPlaybackPolicyEnforcer() {
|
||||
return getBindable().getPlaybackPolicyEnforcer();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public @Override GiphyMp4Projection getProjection(@NonNull RecyclerView recyclerView) {
|
||||
return getBindable().getProjection(recyclerView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlayContent() {
|
||||
return getBindable().canPlayContent();
|
||||
}
|
||||
}
|
||||
|
||||
static class StickyHeaderViewHolder extends RecyclerView.ViewHolder {
|
||||
@@ -688,6 +738,6 @@ public class ConversationAdapter
|
||||
|
||||
interface ItemClickListener extends BindableConversationItem.EventListener {
|
||||
void onItemClick(ConversationMessage item);
|
||||
void onItemLongClick(View maskTarget, ConversationMessage item);
|
||||
void onItemLongClick(View itemView, ConversationMessage item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ import org.thoughtcrime.securesms.VerifyIdentityActivity;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.components.ConversationScrollToView;
|
||||
import org.thoughtcrime.securesms.components.ConversationTypingView;
|
||||
import org.thoughtcrime.securesms.components.MaskView;
|
||||
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
|
||||
@@ -97,6 +98,10 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicy;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionPlayerHolder;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackController;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionRecycler;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.invite.GroupLinkInviteFriendsBottomSheetDialogFragment;
|
||||
@@ -148,6 +153,7 @@ import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar;
|
||||
import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
@@ -177,6 +183,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
private boolean isReacting;
|
||||
private ActionMode actionMode;
|
||||
private Locale locale;
|
||||
private FrameLayout videoContainer;
|
||||
private RecyclerView list;
|
||||
private RecyclerView.ItemDecoration lastSeenDecoration;
|
||||
private RecyclerView.ItemDecoration inlineDateDecoration;
|
||||
@@ -204,6 +211,8 @@ public class ConversationFragment extends LoggingFragment {
|
||||
private View toolbarShadow;
|
||||
private Stopwatch startupStopwatch;
|
||||
|
||||
private GiphyMp4ProjectionRecycler giphyMp4ProjectionRecycler;
|
||||
|
||||
public static void prepare(@NonNull Context context) {
|
||||
FrameLayout parent = new FrameLayout(context);
|
||||
parent.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT));
|
||||
@@ -226,6 +235,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
final View view = inflater.inflate(R.layout.conversation_fragment, container, false);
|
||||
videoContainer = view.findViewById(R.id.video_container);
|
||||
list = view.findViewById(android.R.id.list);
|
||||
composeDivider = view.findViewById(R.id.compose_divider);
|
||||
|
||||
@@ -250,13 +260,16 @@ public class ConversationFragment extends LoggingFragment {
|
||||
|
||||
typingView = (ConversationTypingView) inflater.inflate(R.layout.conversation_typing_view, container, false);
|
||||
|
||||
giphyMp4ProjectionRecycler = initializeGiphyMp4();
|
||||
|
||||
new ConversationItemSwipeCallback(
|
||||
conversationMessage -> actionMode == null &&
|
||||
MenuState.canReplyToMessage(recipient.get(),
|
||||
MenuState.isActionMessage(conversationMessage.getMessageRecord()),
|
||||
conversationMessage.getMessageRecord(),
|
||||
messageRequestViewModel.shouldShowMessageRequest()),
|
||||
this::handleReplyMessage
|
||||
this::handleReplyMessage,
|
||||
giphyMp4ProjectionRecycler
|
||||
).attachToRecyclerView(list);
|
||||
|
||||
setupListLayoutListeners();
|
||||
@@ -297,6 +310,26 @@ public class ConversationFragment extends LoggingFragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
private @NonNull GiphyMp4ProjectionRecycler initializeGiphyMp4() {
|
||||
int maxPlayback = GiphyMp4PlaybackPolicy.maxSimultaneousPlaybackInConversation();
|
||||
List<GiphyMp4ProjectionPlayerHolder> holders = GiphyMp4ProjectionPlayerHolder.injectVideoViews(requireContext(),
|
||||
getViewLifecycleOwner().getLifecycle(),
|
||||
videoContainer,
|
||||
maxPlayback);
|
||||
GiphyMp4ProjectionRecycler callback = new GiphyMp4ProjectionRecycler(holders);
|
||||
|
||||
GiphyMp4PlaybackController.attach(list, callback, maxPlayback);
|
||||
|
||||
return callback;
|
||||
}
|
||||
|
||||
private @NonNull MaskView.MaskTarget getMaskTarget(@NonNull View itemView) {
|
||||
int adapterPosition = list.getChildAdapterPosition(itemView);
|
||||
View videoPlayer = giphyMp4ProjectionRecycler.getVideoPlayerAtAdapterPosition(adapterPosition);
|
||||
|
||||
return new ConversationItemMaskTarget((ConversationItem) itemView, videoPlayer);
|
||||
}
|
||||
|
||||
private void setupListLayoutListeners() {
|
||||
list.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> setListVerticalTranslation());
|
||||
|
||||
@@ -557,7 +590,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
private void initializeListAdapter() {
|
||||
if (this.recipient != null && this.threadId != -1) {
|
||||
Log.d(TAG, "Initializing adapter for " + recipient.getId());
|
||||
ConversationAdapter adapter = new ConversationAdapter(this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get());
|
||||
ConversationAdapter adapter = new ConversationAdapter(this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get(), new AttachmentMediaSourceFactory(requireContext()));
|
||||
adapter.setPagingController(conversationViewModel.getPagingController());
|
||||
list.setAdapter(adapter);
|
||||
setInlineDateDecoration(adapter);
|
||||
@@ -1191,14 +1224,14 @@ public class ConversationFragment extends LoggingFragment {
|
||||
void onMessageActionToolbarOpened();
|
||||
void onForwardClicked();
|
||||
void onMessageRequest(@NonNull MessageRequestViewModel viewModel);
|
||||
void handleReaction(@NonNull View maskTarget,
|
||||
void handleReaction(@NonNull MaskView.MaskTarget maskTarget,
|
||||
@NonNull MessageRecord messageRecord,
|
||||
@NonNull Toolbar.OnMenuItemClickListener toolbarListener,
|
||||
@NonNull ConversationReactionOverlay.OnHideListener onHideListener);
|
||||
void onCursorChanged();
|
||||
void onListVerticalTranslationChanged(float translationY);
|
||||
void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord);
|
||||
void handleReactionDetails(@NonNull View maskTarget);
|
||||
void handleReactionDetails(@NonNull MaskView.MaskTarget maskTarget);
|
||||
}
|
||||
|
||||
private class ConversationScrollListener extends OnScrollListener {
|
||||
@@ -1288,7 +1321,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemLongClick(View maskTarget, ConversationMessage conversationMessage) {
|
||||
public void onItemLongClick(View itemView, ConversationMessage conversationMessage) {
|
||||
|
||||
if (actionMode != null) return;
|
||||
|
||||
@@ -1304,7 +1337,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
{
|
||||
isReacting = true;
|
||||
list.setLayoutFrozen(true);
|
||||
listener.handleReaction(maskTarget, messageRecord, new ReactionsToolbarListener(conversationMessage), () -> {
|
||||
listener.handleReaction(getMaskTarget(itemView), messageRecord, new ReactionsToolbarListener(conversationMessage), () -> {
|
||||
isReacting = false;
|
||||
list.setLayoutFrozen(false);
|
||||
WindowUtil.setLightStatusBarFromTheme(requireActivity());
|
||||
@@ -1452,7 +1485,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
public void onReactionClicked(@NonNull View reactionTarget, long messageId, boolean isMms) {
|
||||
if (getContext() == null) return;
|
||||
|
||||
listener.handleReactionDetails(reactionTarget);
|
||||
listener.handleReactionDetails(getMaskTarget(reactionTarget));
|
||||
ReactionsBottomSheetDialogFragment.create(messageId, isMms).show(requireFragmentManager(), null);
|
||||
}
|
||||
|
||||
@@ -1571,6 +1604,11 @@ public class ConversationFragment extends LoggingFragment {
|
||||
refreshList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayInlineContent(ConversationMessage conversationMessage) {
|
||||
getListAdapter().playInlineContent(conversationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshList() {
|
||||
|
||||
@@ -55,8 +55,10 @@ import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BindableConversationItem;
|
||||
@@ -70,6 +72,7 @@ import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.BorderlessImageView;
|
||||
import org.thoughtcrime.securesms.components.ConversationItemFooter;
|
||||
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
|
||||
import org.thoughtcrime.securesms.components.CornerMask;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.LinkPreviewView;
|
||||
import org.thoughtcrime.securesms.components.Outliner;
|
||||
@@ -87,6 +90,9 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.Quote;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicy;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Projection;
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.MmsDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.MmsSendJob;
|
||||
@@ -100,6 +106,7 @@ import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.reactions.ReactionsConversationView;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -120,6 +127,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.VibrateUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -198,9 +206,13 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
private final LinkPreviewClickListener linkPreviewClickListener = new LinkPreviewClickListener();
|
||||
private final ViewOnceMessageClickListener revealableClickListener = new ViewOnceMessageClickListener();
|
||||
private final UrlClickListener urlClickListener = new UrlClickListener();
|
||||
private final Rect thumbnailMaskingRect = new Rect();
|
||||
|
||||
private final Context context;
|
||||
|
||||
private MediaSource mediaSource;
|
||||
private boolean canPlayContent;
|
||||
|
||||
public ConversationItem(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -260,7 +272,9 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
@Nullable String searchQuery,
|
||||
boolean pulse,
|
||||
boolean hasWallpaper,
|
||||
boolean isMessageRequestAccepted)
|
||||
boolean isMessageRequestAccepted,
|
||||
@NonNull AttachmentMediaSourceFactory attachmentMediaSourceFactory,
|
||||
boolean allowedToPlayInline)
|
||||
{
|
||||
if (this.recipient != null) this.recipient.removeForeverObserver(this);
|
||||
if (this.conversationRecipient != null) this.conversationRecipient.removeForeverObserver(this);
|
||||
@@ -275,13 +289,15 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
this.conversationRecipient = conversationRecipient.live();
|
||||
this.groupThread = conversationRecipient.isGroup();
|
||||
this.recipient = messageRecord.getIndividualRecipient().live();
|
||||
this.canPlayContent = false;
|
||||
this.mediaSource = null;
|
||||
|
||||
this.recipient.observeForever(this);
|
||||
this.conversationRecipient.observeForever(this);
|
||||
|
||||
setGutterSizes(messageRecord, groupThread);
|
||||
setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, groupThread, hasWallpaper, isMessageRequestAccepted);
|
||||
setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, groupThread, hasWallpaper, isMessageRequestAccepted, attachmentMediaSourceFactory, allowedToPlayInline);
|
||||
setBodyText(messageRecord, searchQuery, isMessageRequestAccepted);
|
||||
setBubbleState(messageRecord, hasWallpaper);
|
||||
setInteractionState(conversationMessage, pulse);
|
||||
@@ -675,12 +691,14 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaAttributes(@NonNull MessageRecord messageRecord,
|
||||
@NonNull Optional<MessageRecord> previousRecord,
|
||||
@NonNull Optional<MessageRecord> nextRecord,
|
||||
boolean isGroupThread,
|
||||
boolean hasWallpaper,
|
||||
boolean messageRequestAccepted)
|
||||
private void setMediaAttributes(@NonNull MessageRecord messageRecord,
|
||||
@NonNull Optional<MessageRecord> previousRecord,
|
||||
@NonNull Optional<MessageRecord> nextRecord,
|
||||
boolean isGroupThread,
|
||||
boolean hasWallpaper,
|
||||
boolean messageRequestAccepted,
|
||||
@Nullable AttachmentMediaSourceFactory attachmentMediaSourceFactory,
|
||||
boolean allowedToPlayInline)
|
||||
{
|
||||
boolean showControls = !messageRecord.isFailed();
|
||||
|
||||
@@ -865,6 +883,16 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
footer.setVisibility(VISIBLE);
|
||||
|
||||
if (attachmentMediaSourceFactory != null &&
|
||||
thumbnailSlides.size() == 1 &&
|
||||
thumbnailSlides.get(0).isVideoGif() &&
|
||||
thumbnailSlides.get(0) instanceof VideoSlide)
|
||||
{
|
||||
canPlayContent = GiphyMp4PlaybackPolicy.autoplay() || allowedToPlayInline;
|
||||
mediaSource = attachmentMediaSourceFactory.createMediaSource(Objects.requireNonNull(thumbnailSlides.get(0).getUri()));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
@@ -1399,6 +1427,68 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
return span;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showProjectionArea() {
|
||||
if (mediaThumbnailStub != null && mediaThumbnailStub.resolved()) {
|
||||
mediaThumbnailStub.get().showThumbnailView();
|
||||
bodyBubble.setMask(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideProjectionArea() {
|
||||
if (mediaThumbnailStub != null && mediaThumbnailStub.resolved()) {
|
||||
mediaThumbnailStub.get().hideThumbnailView();
|
||||
mediaThumbnailStub.get().getDrawingRect(thumbnailMaskingRect);
|
||||
bodyBubble.setMask(thumbnailMaskingRect);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable MediaSource getMediaSource() {
|
||||
return mediaSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GiphyMp4PlaybackPolicyEnforcer getPlaybackPolicyEnforcer() {
|
||||
if (GiphyMp4PlaybackPolicy.autoplay()) {
|
||||
return null;
|
||||
} else {
|
||||
return new GiphyMp4PlaybackPolicyEnforcer(() -> {
|
||||
eventListener.onPlayInlineContent(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAdapterPosition() {
|
||||
throw new UnsupportedOperationException("Do not delegate to this method");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull GiphyMp4Projection getProjection(@NonNull RecyclerView recyclerView) {
|
||||
return GiphyMp4Projection.forView(recyclerView, mediaThumbnailStub.get(), mediaThumbnailStub.get().getCornerMask())
|
||||
.translateX(bodyBubble.getTranslationX());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlayContent() {
|
||||
return mediaThumbnailStub != null && canPlayContent;
|
||||
}
|
||||
|
||||
public @NonNull Rect getThumbnailMaskingRect(@NonNull ViewGroup parent) {
|
||||
Rect rect = new Rect();
|
||||
rect.set(thumbnailMaskingRect);
|
||||
|
||||
parent.offsetDescendantRectToMyCoords(mediaThumbnailStub.get(), rect);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public @NonNull CornerMask getThumbnailCornerMask(@NonNull View view) {
|
||||
return new CornerMask(view, mediaThumbnailStub.get().getCornerMask());
|
||||
}
|
||||
|
||||
private class SharedContactEventListener implements SharedContactView.EventListener {
|
||||
@Override
|
||||
public void onAddToContactsClicked(@NonNull Contact contact) {
|
||||
@@ -1526,6 +1616,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
public void onClick(final View v, final Slide slide) {
|
||||
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
|
||||
performClick();
|
||||
} else if (!canPlayContent && mediaSource != null && eventListener != null) {
|
||||
eventListener.onPlayInlineContent(conversationMessage);
|
||||
} else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
|
||||
Intent intent = new Intent(context, MediaPreviewActivity.class);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
@@ -19,6 +21,9 @@ public class ConversationItemBodyBubble extends LinearLayout {
|
||||
@Nullable private List<Outliner> outliners = Collections.emptyList();
|
||||
@Nullable private OnSizeChangedListener sizeChangedListener;
|
||||
|
||||
private MaskDrawable maskDrawable;
|
||||
private Rect mask;
|
||||
|
||||
public ConversationItemBodyBubble(Context context) {
|
||||
super(context);
|
||||
}
|
||||
@@ -39,6 +44,18 @@ public class ConversationItemBodyBubble extends LinearLayout {
|
||||
this.sizeChangedListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackground(Drawable background) {
|
||||
maskDrawable = new MaskDrawable(background);
|
||||
maskDrawable.setMask(mask);
|
||||
super.setBackground(maskDrawable);
|
||||
}
|
||||
|
||||
public void setMask(@Nullable Rect mask) {
|
||||
this.mask = mask;
|
||||
maskDrawable.setMask(mask);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.components.CornerMask;
|
||||
import org.thoughtcrime.securesms.components.MaskView;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Projection;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class ConversationItemMaskTarget extends MaskView.MaskTarget {
|
||||
|
||||
private final ConversationItem conversationItem;
|
||||
private final View videoContainer;
|
||||
|
||||
public ConversationItemMaskTarget(@NonNull ConversationItem conversationItem,
|
||||
@Nullable View videoContainer)
|
||||
{
|
||||
super(conversationItem);
|
||||
this.conversationItem = conversationItem;
|
||||
this.videoContainer = videoContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull List<View> getAllTargets() {
|
||||
if (videoContainer == null) {
|
||||
return super.getAllTargets();
|
||||
} else {
|
||||
return Arrays.asList(conversationItem, videoContainer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void draw(@NonNull Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
|
||||
if (videoContainer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GiphyMp4Projection projection = conversationItem.getProjection((RecyclerView) conversationItem.getParent());
|
||||
CornerMask cornerMask = projection.getCornerMask();
|
||||
|
||||
canvas.clipRect(conversationItem.bodyBubble.getLeft(),
|
||||
conversationItem.bodyBubble.getTop(),
|
||||
conversationItem.bodyBubble.getRight(),
|
||||
conversationItem.bodyBubble.getTop() + projection.getHeight());
|
||||
|
||||
canvas.drawColor(Color.BLACK);
|
||||
cornerMask.mask(canvas);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4DisplayUpdater;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Playable;
|
||||
import org.thoughtcrime.securesms.util.AccessibilityUtil;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
@@ -28,14 +30,17 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
private final SwipeAvailabilityProvider swipeAvailabilityProvider;
|
||||
private final ConversationItemTouchListener itemTouchListener;
|
||||
private final OnSwipeListener onSwipeListener;
|
||||
private final GiphyMp4DisplayUpdater giphyMp4DisplayUpdater;
|
||||
|
||||
ConversationItemSwipeCallback(@NonNull SwipeAvailabilityProvider swipeAvailabilityProvider,
|
||||
@NonNull OnSwipeListener onSwipeListener)
|
||||
@NonNull OnSwipeListener onSwipeListener,
|
||||
@NonNull GiphyMp4DisplayUpdater giphyMp4DisplayUpdater)
|
||||
{
|
||||
super(0, ItemTouchHelper.END);
|
||||
this.itemTouchListener = new ConversationItemTouchListener(this::updateLatestDownCoordinate);
|
||||
this.swipeAvailabilityProvider = swipeAvailabilityProvider;
|
||||
this.onSwipeListener = onSwipeListener;
|
||||
this.giphyMp4DisplayUpdater = giphyMp4DisplayUpdater;
|
||||
this.shouldTriggerSwipeFeedback = true;
|
||||
this.canTriggerSwipe = true;
|
||||
}
|
||||
@@ -88,12 +93,14 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && isCorrectSwipeDir) {
|
||||
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, Math.abs(dx), sign);
|
||||
updateVideoPlayer(recyclerView, viewHolder);
|
||||
handleSwipeFeedback((ConversationItem) viewHolder.itemView, Math.abs(dx));
|
||||
if (canTriggerSwipe) {
|
||||
setTouchListener(recyclerView, viewHolder, Math.abs(dx));
|
||||
}
|
||||
} else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE || dx == 0) {
|
||||
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, 0, 1);
|
||||
updateVideoPlayer(recyclerView, viewHolder);
|
||||
}
|
||||
|
||||
if (dx == 0) {
|
||||
@@ -102,6 +109,12 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVideoPlayer(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
if (viewHolder instanceof GiphyMp4Playable) {
|
||||
giphyMp4DisplayUpdater.updateDisplay(recyclerView, (GiphyMp4Playable) viewHolder);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSwipeFeedback(@NonNull ConversationItem item, float dx) {
|
||||
if (dx > SWIPE_SUCCESS_DX && shouldTriggerSwipeFeedback) {
|
||||
vibrate(item.getContext());
|
||||
@@ -134,7 +147,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
swipeBack = true;
|
||||
shouldTriggerSwipeFeedback = false;
|
||||
resetProgressIfAnimationsDisabled(viewHolder);
|
||||
resetProgressIfAnimationsDisabled(recyclerView, viewHolder);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
@@ -156,11 +169,12 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
||||
recyclerView.cancelPendingInputEvents();
|
||||
}
|
||||
|
||||
private static void resetProgressIfAnimationsDisabled(RecyclerView.ViewHolder viewHolder) {
|
||||
private void resetProgressIfAnimationsDisabled(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
if (AccessibilityUtil.areAnimationsDisabled(viewHolder.itemView.getContext())) {
|
||||
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView,
|
||||
0f,
|
||||
getSignFromDirection(viewHolder.itemView));
|
||||
updateVideoPlayer(recyclerView, viewHolder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import org.thoughtcrime.securesms.components.MaskView;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
@@ -38,7 +39,7 @@ final class ConversationReactionDelegate {
|
||||
}
|
||||
|
||||
void show(@NonNull Activity activity,
|
||||
@NonNull View maskTarget,
|
||||
@NonNull MaskView.MaskTarget maskTarget,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
@NonNull MessageRecord messageRecord,
|
||||
int maskPaddingBottom)
|
||||
@@ -46,7 +47,7 @@ final class ConversationReactionDelegate {
|
||||
resolveOverlay().show(activity, maskTarget, conversationRecipient, messageRecord, maskPaddingBottom, lastSeenDownPoint);
|
||||
}
|
||||
|
||||
void showMask(@NonNull View maskTarget, int maskPaddingTop, int maskPaddingBottom) {
|
||||
void showMask(@NonNull MaskView.MaskTarget maskTarget, int maskPaddingTop, int maskPaddingBottom) {
|
||||
resolveOverlay().showMask(maskTarget, maskPaddingTop, maskPaddingBottom);
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||
}
|
||||
|
||||
public void show(@NonNull Activity activity,
|
||||
@NonNull View maskTarget,
|
||||
@NonNull MaskView.MaskTarget maskTarget,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
@NonNull MessageRecord messageRecord,
|
||||
int maskPaddingBottom,
|
||||
@@ -209,7 +209,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public void showMask(@NonNull View maskTarget, int maskPaddingTop, int maskPaddingBottom) {
|
||||
public void showMask(@NonNull MaskView.MaskTarget maskTarget, int maskPaddingTop, int maskPaddingBottom) {
|
||||
maskView.setPadding(0, maskPaddingTop, 0, maskPaddingBottom);
|
||||
maskView.setTarget(maskTarget);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
|
||||
@@ -29,6 +30,7 @@ import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil;
|
||||
import org.thoughtcrime.securesms.database.model.LiveUpdateMessage;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.UpdateDescription;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Projection;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -40,6 +42,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -102,7 +105,9 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
@Nullable String searchQuery,
|
||||
boolean pulseMention,
|
||||
boolean hasWallpaper,
|
||||
boolean isMessageRequestAccepted)
|
||||
boolean isMessageRequestAccepted,
|
||||
@NonNull AttachmentMediaSourceFactory attachmentMediaSourceFactory,
|
||||
boolean allowedToPlayInline)
|
||||
{
|
||||
this.batchSelected = batchSelected;
|
||||
|
||||
@@ -182,6 +187,30 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
public void unbind() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showProjectionArea() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideProjectionArea() {
|
||||
throw new UnsupportedOperationException("Call makes no sense for a conversation update item");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAdapterPosition() {
|
||||
throw new UnsupportedOperationException("Don't delegate to this method.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull GiphyMp4Projection getProjection(@NonNull RecyclerView recyclerView) {
|
||||
throw new UnsupportedOperationException("ConversationUpdateItems cannot be projected into.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlayContent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static final class RecipientObserverManager {
|
||||
|
||||
private final Observer<Recipient> recipientObserver;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Drawable which lets you punch a hole through another drawable.
|
||||
*/
|
||||
public final class MaskDrawable extends Drawable {
|
||||
|
||||
private final RectF bounds = new RectF();
|
||||
private final Path clipPath = new Path();
|
||||
|
||||
private Rect clipRect;
|
||||
private float[] clipPathRadii;
|
||||
|
||||
private final Drawable wrapped;
|
||||
|
||||
public MaskDrawable(@NonNull Drawable wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (clipRect == null) {
|
||||
wrapped.draw(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.save();
|
||||
|
||||
if (clipPathRadii != null) {
|
||||
clipPath.reset();
|
||||
bounds.set(clipRect);
|
||||
clipPath.addRoundRect(bounds, clipPathRadii, Path.Direction.CW);
|
||||
canvas.clipPath(clipPath, Region.Op.DIFFERENCE);
|
||||
} else {
|
||||
canvas.clipRect(clipRect, Region.Op.DIFFERENCE);
|
||||
}
|
||||
|
||||
wrapped.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
wrapped.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||
wrapped.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return wrapped.getOpacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBounds(int left, int top, int right, int bottom) {
|
||||
super.setBounds(left, top, right, bottom);
|
||||
wrapped.setBounds(left, top, right, bottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPadding(@NonNull Rect padding) {
|
||||
return wrapped.getPadding(padding);
|
||||
}
|
||||
|
||||
public void setMask(@Nullable Rect mask) {
|
||||
this.clipRect = new Rect(mask);
|
||||
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public void setCorners(@Nullable float[] clipPathRadii) {
|
||||
this.clipPathRadii = clipPathRadii;
|
||||
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user